Merge "Ensure libidmap2 exists for all ABIs"
diff --git a/Android.bp b/Android.bp
index c0a2abb..3d0188a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -108,7 +108,7 @@
":android.security.legacykeystore-java-source",
":android.security.maintenance-java-source",
":android.security.metrics-java-source",
- ":android.system.keystore2-V1-java-source",
+ ":android.system.keystore2-V3-java-source",
":credstore_aidl",
":dumpstate_aidl",
":framework_native_aidl",
diff --git a/OWNERS b/OWNERS
index d4d1936..09a721f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
narayan@google.com #{LAST_RESORT_SUGGESTION}
ogunwale@google.com #{LAST_RESORT_SUGGESTION}
roosa@google.com #{LAST_RESORT_SUGGESTION}
+smoreland@google.com #{LAST_RESORT_SUGGESTION}
svetoslavganov@android.com #{LAST_RESORT_SUGGESTION}
svetoslavganov@google.com #{LAST_RESORT_SUGGESTION}
yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 0e08496..272b4f6 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -328,10 +328,12 @@
java_library {
name: "android_test_stubs_current",
- // Modules do not have test APIs, but we want to include their SystemApis, like we include
- // the SystemApi of framework-non-updatable-sources.
static_libs: [
- "all-modules-system-stubs",
+ // Updatable modules do not have test APIs, but we want to include their SystemApis, like we
+ // include the SystemApi of framework-non-updatable-sources.
+ "all-updatable-modules-system-stubs",
+ // Non-updatable modules on the other hand can have test APIs, so include their test-stubs.
+ "all-non-updatable-modules-test-stubs",
"android-non-updatable.stubs.test",
"private-stub-annotations-jar",
],
diff --git a/apct-tests/perftests/windowmanager/AndroidManifest.xml b/apct-tests/perftests/windowmanager/AndroidManifest.xml
index 95ede34..532a0fc 100644
--- a/apct-tests/perftests/windowmanager/AndroidManifest.xml
+++ b/apct-tests/perftests/windowmanager/AndroidManifest.xml
@@ -17,6 +17,10 @@
package="com.android.perftests.wm">
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.READ_LOGS" />
+ <!-- For perfetto trace files -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<uses-library android:name="android.test.runner" />
@@ -26,6 +30,9 @@
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
</activity>
+
+ <activity android:name="android.wm.InTaskTransitionTest$TestActivity"
+ android:process=":test" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
new file mode 100644
index 0000000..2d2cf1c8
--- /dev/null
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.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 android.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.PerfTestActivity;
+import android.view.WindowManagerGlobal;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+/** Measure the performance of warm launch activity in the same task. */
+public class InTaskTransitionTest extends WindowManagerPerfTestBase
+ implements RemoteCallback.OnResultListener {
+
+ private static final long TIMEOUT_MS = 5000;
+
+ @Rule
+ public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+ private final TransitionMetricsReader mMetricsReader = new TransitionMetricsReader();
+
+ @Test
+ @ManualBenchmarkState.ManualBenchmarkTest(
+ targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+ statsReport = @ManualBenchmarkState.StatsReport(
+ flags = ManualBenchmarkState.StatsReport.FLAG_ITERATION
+ | ManualBenchmarkState.StatsReport.FLAG_MEAN
+ | ManualBenchmarkState.StatsReport.FLAG_MAX))
+ public void testStartActivityInSameTask() {
+ final Context context = getInstrumentation().getContext();
+ final Activity activity = getInstrumentation().startActivitySync(
+ new Intent(context, PerfTestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ final Intent next = new Intent(context, TestActivity.class);
+ next.putExtra(TestActivity.CALLBACK, new RemoteCallback(this));
+
+ final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long measuredTimeNs = 0;
+
+ boolean readerStarted = false;
+ while (state.keepRunning(measuredTimeNs)) {
+ if (!readerStarted && !state.isWarmingUp()) {
+ mMetricsReader.setCheckpoint();
+ readerStarted = true;
+ }
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ activity.startActivity(next);
+ synchronized (mMetricsReader) {
+ try {
+ mMetricsReader.wait(TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+ }
+
+ for (TransitionMetricsReader.TransitionMetrics metrics : mMetricsReader.getMetrics()) {
+ if (metrics.mTransitionDelayMs > 0) {
+ state.addExtraResult("transitionDelayMs", metrics.mTransitionDelayMs);
+ }
+ if (metrics.mWindowsDrawnDelayMs > 0) {
+ state.addExtraResult("windowsDrawnDelayMs", metrics.mWindowsDrawnDelayMs);
+ }
+ }
+ }
+
+ @Override
+ public void onResult(Bundle result) {
+ // The test activity is destroyed.
+ synchronized (mMetricsReader) {
+ mMetricsReader.notifyAll();
+ }
+ }
+
+ /** The test activity runs on a different process to trigger metrics logs. */
+ public static class TestActivity extends Activity implements Runnable {
+ static final String CALLBACK = "callback";
+
+ private RemoteCallback mCallback;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mCallback = getIntent().getParcelableExtra(CALLBACK, RemoteCallback.class);
+ if (mCallback != null) {
+ Looper.myLooper().getQueue().addIdleHandler(() -> {
+ new Thread(this).start();
+ return false;
+ });
+ }
+ }
+
+ @Override
+ public void run() {
+ // Wait until transition animation is finished and then finish self.
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .syncInputTransactions(true /* waitForAnimations */);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ finish();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (mCallback != null) {
+ getMainThreadHandler().post(() -> mCallback.sendResult(null));
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
index 4b1982f..aea0326 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,10 +18,17 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
+
import android.app.Activity;
import android.content.Intent;
+import android.metrics.LogMaker;
+import android.metrics.MetricsReader;
import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.WindowPerfTestBase;
+import android.util.SparseArray;
import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
@@ -31,6 +38,7 @@
import org.junit.runners.model.Statement;
import java.io.File;
+import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase extends WindowPerfTestBase {
@@ -124,4 +132,42 @@
}
}
}
+
+ static class TransitionMetricsReader {
+ final MetricsReader mMetricsReader = new MetricsReader();
+
+ static class TransitionMetrics {
+ int mTransitionDelayMs;
+ int mWindowsDrawnDelayMs;
+ }
+
+ TransitionMetrics[] getMetrics() {
+ mMetricsReader.read(0);
+ final ArrayList<LogMaker> logs = new ArrayList<>();
+ final LogMaker logTemplate = new LogMaker(APP_TRANSITION);
+ while (mMetricsReader.hasNext()) {
+ final LogMaker b = mMetricsReader.next();
+ if (logTemplate.isSubsetOf(b)) {
+ logs.add(b);
+ }
+ }
+
+ final TransitionMetrics[] infoArray = new TransitionMetrics[logs.size()];
+ for (int i = 0; i < infoArray.length; i++) {
+ final LogMaker log = logs.get(i);
+ final SparseArray<Object> data = log.getEntries();
+ final TransitionMetrics info = new TransitionMetrics();
+ infoArray[i] = info;
+ info.mTransitionDelayMs =
+ (int) data.get(APP_TRANSITION_DELAY_MS, -1);
+ info.mWindowsDrawnDelayMs =
+ (int) data.get(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, -1);
+ }
+ return infoArray;
+ }
+
+ void setCheckpoint() {
+ mMetricsReader.checkpoint();
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 0c65b99..2d3201a 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -36,7 +36,7 @@
* Note android.app.job is the better package to put this class, but we can't move it there
* because that'd break robolectric. Grr.
*
- * @hide
+ * @hide
*/
public class JobSchedulerImpl extends JobScheduler {
IJobScheduler mBinder;
@@ -109,6 +109,15 @@
}
@Override
+ public int getPendingJobReason(int jobId) {
+ try {
+ return mBinder.getPendingJobReason(jobId);
+ } catch (RemoteException e) {
+ return PENDING_JOB_REASON_UNDEFINED;
+ }
+ }
+
+ @Override
public boolean canRunLongJobs() {
try {
return mBinder.canRunLongJobs(mContext.getOpPackageName());
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index a3390b7..96494ec 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -16,6 +16,7 @@
package android.app.job;
+import android.app.Notification;
import android.app.job.JobWorkItem;
/**
@@ -104,4 +105,17 @@
*/
void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
long transferredDownloadBytes, long transferredUploadBytes);
+ /**
+ * Provide JobScheduler with a notification to post and tie to this job's
+ * lifecycle.
+ * This is required for all user-initiated job and optional for other jobs.
+ *
+ * @param jobId Unique integer used to identify this job.
+ * @param notificationId The ID for this notification, as per
+ * {@link android.app.NotificationManager#notify(int, Notification)}.
+ * @param notification The notification to be displayed.
+ * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+ */
+ void setNotification(int jobId, int notificationId,
+ in Notification notification, int jobEndNotificationPolicy);
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index d2be32e..bf29dc9 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -33,6 +33,7 @@
void cancelAll();
ParceledListSlice getAllPendingJobs();
JobInfo getPendingJob(int jobId);
+ int getPendingJobReason(int jobId);
boolean canRunLongJobs(String packageName);
boolean hasRunLongJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 13b6652..659db9f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -23,10 +23,14 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.ClipData;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
import android.os.Build;
import android.os.Bundle;
import android.os.PersistableBundle;
@@ -133,6 +137,132 @@
*/
public static final int RESULT_SUCCESS = 1;
+ /** The job doesn't exist. */
+ public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2;
+ /** The job is currently running and is therefore not pending. */
+ public static final int PENDING_JOB_REASON_EXECUTING = -1;
+ /**
+ * There is no known reason why the job is pending.
+ * If additional reasons are added on newer Android versions, the system may return this reason
+ * to apps whose target SDK is not high enough to expect that reason.
+ */
+ public static final int PENDING_JOB_REASON_UNDEFINED = 0;
+ /**
+ * The app is in a state that prevents the job from running
+ * (eg. the {@link JobService} component is disabled).
+ */
+ public static final int PENDING_JOB_REASON_APP = 1;
+ /**
+ * The current standby bucket prevents the job from running.
+ *
+ * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+ */
+ public static final int PENDING_JOB_REASON_APP_STANDBY = 2;
+ /**
+ * The app is restricted from running in the background.
+ *
+ * @see ActivityManager#isBackgroundRestricted()
+ * @see PackageManager#isInstantApp()
+ */
+ public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3;
+ /**
+ * The requested battery-not-low constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4;
+ /**
+ * The requested charging constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresCharging(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5;
+ /**
+ * The requested connectivity constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+ * @see JobInfo.Builder#setRequiredNetworkType(int)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6;
+ /**
+ * The requested content trigger constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#addTriggerContentUri(JobInfo.TriggerContentUri)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7;
+ /**
+ * The requested idle constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+ /**
+ * The minimum latency has not transpired.
+ *
+ * @see JobInfo.Builder#setMinimumLatency(long)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9;
+ /**
+ * The system's estimate of when the app will be launched is far away enough to warrant delaying
+ * this job.
+ *
+ * @see JobInfo#isPrefetch()
+ * @see JobInfo.Builder#setPrefetch(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10;
+ /**
+ * The requested storage-not-low constraint is not satisfied.
+ *
+ * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+ */
+ public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11;
+ /**
+ * The job is being deferred due to the device state (eg. Doze, battery saver, memory usage,
+ * thermal status, etc.).
+ */
+ public static final int PENDING_JOB_REASON_DEVICE_STATE = 12;
+ /**
+ * JobScheduler thinks it can defer this job to a more optimal running time.
+ */
+ public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13;
+ /**
+ * The app has consumed all of its current quota.
+ *
+ * @see UsageStatsManager#getAppStandbyBucket()
+ * @see JobParameters#STOP_REASON_QUOTA
+ */
+ public static final int PENDING_JOB_REASON_QUOTA = 14;
+ /**
+ * JobScheduler is respecting one of the user's actions (eg. force stop or adb shell commands)
+ * to defer this job.
+ */
+ public static final int PENDING_JOB_REASON_USER = 15;
+
+ /** @hide */
+ @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
+ PENDING_JOB_REASON_UNDEFINED,
+ PENDING_JOB_REASON_APP,
+ PENDING_JOB_REASON_APP_STANDBY,
+ PENDING_JOB_REASON_BACKGROUND_RESTRICTION,
+ PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+ PENDING_JOB_REASON_CONSTRAINT_CHARGING,
+ PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY,
+ PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER,
+ PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE,
+ PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY,
+ PENDING_JOB_REASON_CONSTRAINT_PREFETCH,
+ PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+ PENDING_JOB_REASON_DEVICE_STATE,
+ PENDING_JOB_REASON_EXECUTING,
+ PENDING_JOB_REASON_INVALID_JOB_ID,
+ PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
+ PENDING_JOB_REASON_QUOTA,
+ PENDING_JOB_REASON_USER,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PendingJobReason {
+ }
+
/**
* Schedule a job to be executed. Will replace any currently scheduled job with the same
* ID with the new information in the {@link JobInfo}. If a job with the given ID is currently
@@ -250,6 +380,15 @@
public abstract @Nullable JobInfo getPendingJob(int jobId);
/**
+ * Returns a reason why the job is pending and not currently executing. If there are multiple
+ * reasons why a job may be pending, this will only return one of them.
+ */
+ @PendingJobReason
+ public int getPendingJobReason(int jobId) {
+ return PENDING_JOB_REASON_UNDEFINED;
+ }
+
+ /**
* Returns {@code true} if the calling app currently holds the
* {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
*/
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index bad641c..e88e979 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -19,13 +19,18 @@
import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
import android.annotation.BytesLong;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
import android.os.IBinder;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
* <p>This is the base class that handles asynchronous requests that were previously scheduled. You
@@ -63,6 +68,32 @@
public static final String PERMISSION_BIND =
"android.permission.BIND_JOB_SERVICE";
+ /**
+ * Detach the notification supplied to
+ * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+ * The notification will remain shown even after JobScheduler stops the job.
+ *
+ * @hide
+ */
+ public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0;
+ /**
+ * Cancel and remove the notification supplied to
+ * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+ * The notification will be removed from the notification shade.
+ *
+ * @hide
+ */
+ public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1;
+
+ /** @hide */
+ @IntDef(prefix = {"JOB_END_NOTIFICATION_POLICY_"}, value = {
+ JOB_END_NOTIFICATION_POLICY_DETACH,
+ JOB_END_NOTIFICATION_POLICY_REMOVE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface JobEndNotificationPolicy {
+ }
+
private JobServiceEngine mEngine;
/** @hide */
@@ -84,9 +115,9 @@
public long getTransferredDownloadBytes(@NonNull JobParameters params,
@Nullable JobWorkItem item) {
if (item == null) {
- return JobService.this.getTransferredDownloadBytes();
+ return JobService.this.getTransferredDownloadBytes(params);
} else {
- return JobService.this.getTransferredDownloadBytes(item);
+ return JobService.this.getTransferredDownloadBytes(params, item);
}
}
@@ -95,9 +126,9 @@
public long getTransferredUploadBytes(@NonNull JobParameters params,
@Nullable JobWorkItem item) {
if (item == null) {
- return JobService.this.getTransferredUploadBytes();
+ return JobService.this.getTransferredUploadBytes(params);
} else {
- return JobService.this.getTransferredUploadBytes(item);
+ return JobService.this.getTransferredUploadBytes(params, item);
}
}
};
@@ -274,7 +305,7 @@
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredDownloadBytes() {
+ public long getTransferredDownloadBytes(@NonNull JobParameters params) {
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.
@@ -298,7 +329,7 @@
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredUploadBytes() {
+ public long getTransferredUploadBytes(@NonNull JobParameters params) {
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.
@@ -324,9 +355,10 @@
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+ public long getTransferredDownloadBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item) {
if (item == null) {
- return getTransferredDownloadBytes();
+ return getTransferredDownloadBytes(params);
}
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
@@ -353,9 +385,10 @@
*/
// TODO(255371817): specify the actual time JS will wait for progress before requesting
@BytesLong
- public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+ public long getTransferredUploadBytes(@NonNull JobParameters params,
+ @NonNull JobWorkItem item) {
if (item == null) {
- return getTransferredUploadBytes();
+ return getTransferredUploadBytes(params);
}
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
@@ -364,4 +397,37 @@
}
return 0;
}
+
+ /**
+ * Provide JobScheduler with a notification to post and tie to this job's lifecycle.
+ * This is required for all user-initiated jobs
+ * (scheduled via {link JobInfo.Builder#setUserInitiated(boolean)}) and optional for
+ * other jobs. If the app does not call this method for a required notification within
+ * 10 seconds after {@link #onStartJob(JobParameters)} is called,
+ * the system will trigger an ANR and stop this job.
+ *
+ * <p>
+ * Note that certain types of jobs
+ * (e.g. {@link JobInfo.Builder#setDataTransfer data transfer jobs}) may require the
+ * notification to have certain characteristics and their documentation will state
+ * any such requirements.
+ *
+ * <p>
+ * JobScheduler will not remember this notification after the job has finished running,
+ * so apps must call this every time the job is started (if required or desired).
+ *
+ * @param params The parameters identifying this job, as supplied to
+ * the job in the {@link #onStartJob(JobParameters)} callback.
+ * @param notificationId The ID for this notification, as per
+ * {@link android.app.NotificationManager#notify(int,
+ * Notification)}.
+ * @param notification The notification to be displayed.
+ * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+ * @hide
+ */
+ public final void setNotification(@NonNull JobParameters params, int notificationId,
+ @NonNull Notification notification,
+ @JobEndNotificationPolicy int jobEndNotificationPolicy) {
+ mEngine.setNotification(params, notificationId, notification, jobEndNotificationPolicy);
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 83296a6..53e452f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -21,6 +21,7 @@
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.Service;
import android.compat.Compatibility;
import android.content.Intent;
@@ -73,6 +74,8 @@
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;
+ /** Message that the client wants to give JobScheduler a notification to tie to the job. */
+ private static final int MSG_SET_NOTIFICATION = 7;
private final IJobService mBinder;
@@ -250,6 +253,24 @@
args.recycle();
break;
}
+ case MSG_SET_NOTIFICATION: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final JobParameters params = (JobParameters) args.arg1;
+ final Notification notification = (Notification) args.arg2;
+ IJobCallback callback = params.getCallback();
+ if (callback != null) {
+ try {
+ callback.setNotification(params.getJobId(),
+ args.argi1, notification, args.argi2);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error providing notification: binder has gone away.");
+ }
+ } else {
+ Log.e(TAG, "setNotification() called for a nonexistent job.");
+ }
+ args.recycle();
+ break;
+ }
default:
Log.e(TAG, "Unrecognised message received.");
break;
@@ -432,4 +453,27 @@
args.argl2 = uploadBytes;
mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
}
-}
\ No newline at end of file
+
+ /**
+ * Give JobScheduler a notification to tie to this job's lifecycle.
+ *
+ * @hide
+ * @see JobService#setNotification(JobParameters, int, Notification, int)
+ */
+ public void setNotification(@NonNull JobParameters params, int notificationId,
+ @NonNull Notification notification,
+ @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+ if (params == null) {
+ throw new NullPointerException("params");
+ }
+ if (notification == null) {
+ throw new NullPointerException("notification");
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = params;
+ args.arg2 = notification;
+ args.argi1 = notificationId;
+ args.argi2 = jobEndNotificationPolicy;
+ mHandler.obtainMessage(MSG_SET_NOTIFICATION, args).sendToTarget();
+ }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index dcc6aa6..e2d302f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -4821,6 +4821,9 @@
Binder.restoreCallingIdentity(token);
}
} else {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return -1;
+ }
synchronized (this) {
for (int j=0; j<mPowerSaveWhitelistAppsExceptIdle.size(); j++) {
pw.print("system-excidle,");
@@ -4882,6 +4885,9 @@
pw.println("[-r] requires a package name");
return -1;
} else {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return -1;
+ }
dumpTempWhitelistSchedule(pw, false);
}
} else if ("except-idle-whitelist".equals(cmd)) {
@@ -4957,6 +4963,9 @@
Binder.restoreCallingIdentity(token);
}
} else {
+ if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) {
+ return -1;
+ }
synchronized (this) {
for (int j = 0; j < mPowerSaveWhitelistApps.size(); j++) {
pw.print(mPowerSaveWhitelistApps.keyAt(j));
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 f9dd0b3..47f6890 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1175,7 +1175,7 @@
if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
&& restriction.isJobRestricted(jobStatus)) {
- jsc.cancelExecutingJobLocked(restriction.getReason(),
+ jsc.cancelExecutingJobLocked(restriction.getStopReason(),
restriction.getInternalReason(),
JobParameters.getInternalReasonCodeDescription(
restriction.getInternalReason()));
@@ -1208,7 +1208,7 @@
final JobRestriction restriction = mService.checkIfRestricted(running);
if (restriction != null) {
final int internalReasonCode = restriction.getInternalReason();
- serviceContext.cancelExecutingJobLocked(restriction.getReason(),
+ serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
internalReasonCode,
"restricted due to "
+ JobParameters.getInternalReasonCodeDescription(
@@ -1324,6 +1324,7 @@
mActivePkgStats.add(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
packageStats);
+ mService.resetPendingJobReasonCache(jobStatus);
}
if (mService.getPendingJobQueue().remove(jobStatus)) {
mService.mJobPackageTracker.noteNonpending(jobStatus);
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 ee08f85..9fb2af7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -363,6 +363,9 @@
@GuardedBy("mLock")
private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
+ @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
+ private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+
/**
* Named indices into standby bucket arrays, for clarity in referring to
* specific buckets' bookkeeping.
@@ -1357,6 +1360,134 @@
}
}
+ @JobScheduler.PendingJobReason
+ private int getPendingJobReason(int uid, int jobId) {
+ int reason;
+ // Some apps may attempt to query this frequently, so cache the reason under a separate lock
+ // so that the rest of JS processing isn't negatively impacted.
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+ if (jobIdToReason != null) {
+ reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+ if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
+ return reason;
+ }
+ }
+ }
+ synchronized (mLock) {
+ reason = getPendingJobReasonLocked(uid, jobId);
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+ }
+ }
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+ if (jobIdToReason == null) {
+ jobIdToReason = new SparseIntArray();
+ mPendingJobReasonCache.put(uid, jobIdToReason);
+ }
+ jobIdToReason.put(jobId, reason);
+ }
+ return reason;
+ }
+
+ @JobScheduler.PendingJobReason
+ @GuardedBy("mLock")
+ private int getPendingJobReasonLocked(int uid, int jobId) {
+ // Very similar code to isReadyToBeExecutedLocked.
+
+ JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+ if (job == null) {
+ // Job doesn't exist.
+ return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+ }
+
+ if (isCurrentlyRunningLocked(job)) {
+ return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+ }
+
+ final boolean jobReady = job.isReady();
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " ready=" + jobReady);
+ }
+
+ if (!jobReady) {
+ return job.getPendingJobReason();
+ }
+
+ final boolean userStarted = areUsersStartedLocked(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " userStarted=" + userStarted);
+ }
+ if (!userStarted) {
+ return JobScheduler.PENDING_JOB_REASON_USER;
+ }
+
+ final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " backingUp=" + backingUp);
+ }
+
+ if (backingUp) {
+ // TODO: Should we make a special reason for this?
+ return JobScheduler.PENDING_JOB_REASON_APP;
+ }
+
+ JobRestriction restriction = checkIfRestricted(job);
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " restriction=" + restriction);
+ }
+ if (restriction != null) {
+ return restriction.getPendingReason();
+ }
+
+ // The following can be a little more expensive (especially jobActive, since we need to
+ // go through the array of all potentially active jobs), so we are doing them
+ // later... but still before checking with the package manager!
+ final boolean jobPending = mPendingJobQueue.contains(job);
+
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " pending=" + jobPending);
+ }
+
+ if (jobPending) {
+ // We haven't started the job for some reason. Presumably, there are too many jobs
+ // running.
+ return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+ }
+
+ final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " active=" + jobActive);
+ }
+ if (jobActive) {
+ return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ }
+
+ // Validate that the defined package+service is still present & viable.
+ final boolean componentUsable = isComponentUsable(job);
+
+ if (DEBUG) {
+ Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+ + " componentUsable=" + componentUsable);
+ }
+ if (!componentUsable) {
+ return JobScheduler.PENDING_JOB_REASON_APP;
+ }
+
+ return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ }
+
public JobInfo getPendingJob(int uid, int jobId) {
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
@@ -1389,6 +1520,9 @@
synchronized (mLock) {
mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
}
+ synchronized (mPendingJobReasonCache) {
+ mPendingJobReasonCache.clear();
+ }
}
private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
@@ -1874,6 +2008,8 @@
jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
final boolean update = lastJob != null;
mJobs.add(jobStatus);
+ // Clear potentially cached INVALID_JOB_ID reason.
+ resetPendingJobReasonCache(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
@@ -1895,6 +2031,13 @@
// Deal with any remaining work items in the old job.
jobStatus.stopTrackingJobLocked(incomingJob);
+ synchronized (mPendingJobReasonCache) {
+ SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+ if (reasonCache != null) {
+ reasonCache.delete(jobStatus.getJobId());
+ }
+ }
+
// Remove from store as well as controllers.
final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
if (!removed) {
@@ -1917,6 +2060,16 @@
return removed;
}
+ /** Remove the pending job reason for this job from the cache. */
+ void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
+ synchronized (mPendingJobReasonCache) {
+ final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
+ if (reasons != null) {
+ reasons.delete(jobStatus.getJobId());
+ }
+ }
+ }
+
/** Return {@code true} if the specified job is currently executing. */
@GuardedBy("mLock")
public boolean isCurrentlyRunningLocked(JobStatus job) {
@@ -2210,11 +2363,20 @@
public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
if (changedJobs == null) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ synchronized (mPendingJobReasonCache) {
+ mPendingJobReasonCache.clear();
+ }
} else if (changedJobs.size() > 0) {
synchronized (mLock) {
mChangedJobList.addAll(changedJobs);
}
mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
+ synchronized (mPendingJobReasonCache) {
+ for (int i = changedJobs.size() - 1; i >= 0; --i) {
+ final JobStatus job = changedJobs.valueAt(i);
+ resetPendingJobReasonCache(job);
+ }
+ }
}
}
@@ -2568,6 +2730,23 @@
if (DEBUG) {
Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
}
+ final int numRunnableJobs = runnableJobs.size();
+ if (numRunnableJobs > 0) {
+ synchronized (mPendingJobReasonCache) {
+ for (int i = 0; i < numRunnableJobs; ++i) {
+ final JobStatus job = runnableJobs.get(i);
+ SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+ if (reasons == null) {
+ reasons = new SparseIntArray();
+ mPendingJobReasonCache.put(job.getUid(), reasons);
+ }
+ // We're force batching these jobs, so consider it an optimization
+ // policy reason.
+ reasons.put(job.getJobId(),
+ JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ }
+ }
+ }
}
// Be ready for next time
@@ -3370,6 +3549,18 @@
}
@Override
+ public int getPendingJobReason(int jobId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public JobInfo getPendingJob(int jobId) throws RemoteException {
final int uid = Binder.getCallingUid();
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 9aa6b1c..b20eedc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -17,6 +17,8 @@
package com.android.server.job;
import static android.app.job.JobInfo.getPriorityString;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE;
import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
@@ -24,6 +26,7 @@
import android.annotation.BytesLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
import android.app.job.JobInfo;
@@ -58,6 +61,7 @@
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.job.controllers.JobStatus;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.tare.EconomicPolicy;
import com.android.server.tare.EconomyManagerInternal;
import com.android.server.tare.JobSchedulerEconomicPolicy;
@@ -117,6 +121,7 @@
private final EconomyManagerInternal mEconomyManagerInternal;
private final JobPackageTracker mJobPackageTracker;
private final PowerManager mPowerManager;
+ private final NotificationManagerInternal mNotificationManagerInternal;
private PowerManager.WakeLock mWakeLock;
// Execution state.
@@ -169,6 +174,11 @@
/** The absolute maximum amount of time the job can run */
private long mMaxExecutionTimeMillis;
+ private int mNotificationId;
+ private Notification mNotification;
+ private int mNotificationPid;
+ private int mNotificationJobStopPolicy;
+
/**
* The stop reason for a pending cancel. If there's not pending cancel, then the value should be
* {@link JobParameters#STOP_REASON_UNDEFINED}.
@@ -235,6 +245,12 @@
long downloadBytes, long uploadBytes) {
doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
}
+
+ @Override
+ public void setNotification(int jobId, int notificationId,
+ Notification notification, int jobEndNotificationPolicy) {
+ doSetNotification(this, jobId, notificationId, notification, jobEndNotificationPolicy);
+ }
}
JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
@@ -244,6 +260,7 @@
mService = service;
mBatteryStats = batteryStats;
mEconomyManagerInternal = LocalServices.getService(EconomyManagerInternal.class);
+ mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
mJobPackageTracker = tracker;
mCallbackHandler = new JobServiceHandler(looper);
mJobConcurrencyManager = concurrencyManager;
@@ -593,6 +610,49 @@
}
}
+ private void doSetNotification(JobCallback cb, int jodId, int notificationId,
+ Notification notification, int jobEndNotificationPolicy) {
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (!verifyCallerLocked(cb)) {
+ return;
+ }
+ if (callingUid != mRunningJob.getUid()) {
+ Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID...");
+ throw new SecurityException("Can't post notification on behalf of another app");
+ }
+ if (notification == null) {
+ throw new NullPointerException("notification");
+ }
+ if (notification.getSmallIcon() == null) {
+ throw new IllegalArgumentException("small icon required");
+ }
+ final String callingPkgName = mRunningJob.getServiceComponent().getPackageName();
+ if (null == mNotificationManagerInternal.getNotificationChannel(
+ callingPkgName, callingUid, notification.getChannelId())) {
+ throw new IllegalArgumentException("invalid notification channel");
+ }
+ if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH
+ && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) {
+ throw new IllegalArgumentException("invalid job end notification policy");
+ }
+ // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
+ mNotificationManagerInternal.enqueueNotification(
+ callingPkgName, callingPkgName, callingUid, callingPid, /* tag */ null,
+ notificationId, notification, UserHandle.getUserId(callingUid));
+ mNotificationId = notificationId;
+ mNotification = notification;
+ mNotificationPid = callingPid;
+ mNotificationJobStopPolicy = jobEndNotificationPolicy;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
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
@@ -1116,6 +1176,13 @@
JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
String.valueOf(mRunningJob.getJobId()));
}
+ if (mNotification != null
+ && mNotificationJobStopPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+ final String callingPkgName = completedJob.getServiceComponent().getPackageName();
+ mNotificationManagerInternal.cancelNotification(
+ callingPkgName, callingPkgName, completedJob.getUid(), mNotificationPid,
+ /* tag */ null, mNotificationId, UserHandle.getUserId(completedJob.getUid()));
+ }
if (mWakeLock != null) {
mWakeLock.release();
}
@@ -1133,6 +1200,7 @@
mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
mPendingInternalStopReason = 0;
mPendingDebugStopReason = null;
+ mNotification = null;
removeOpTimeOutLocked();
mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
@@ -1157,6 +1225,7 @@
private void scheduleOpTimeOutLocked() {
removeOpTimeOutLocked();
+ // TODO(260848384): enforce setNotification timeout for user-initiated jobs
final long timeoutMillis;
switch (mVerb) {
case VERB_EXECUTING:
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 145ac52..a1153e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -91,8 +91,11 @@
/** 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_";
+ @VisibleForTesting
+ static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
private static final int ALL_UIDS = -1;
+ @VisibleForTesting
+ static final int INVALID_UID = -2;
final Object mLock;
final Object mWriteScheduleLock; // used solely for invariants around write scheduling
@@ -529,6 +532,25 @@
return values;
}
+ @VisibleForTesting
+ static int extractUidFromJobFileName(@NonNull File file) {
+ final String fileName = file.getName();
+ if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) {
+ try {
+ final int subEnd = fileName.length() - 4; // -4 for ".xml"
+ final int uid = Integer.parseInt(
+ fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd));
+ if (uid < 0) {
+ return INVALID_UID;
+ }
+ return uid;
+ } catch (Exception e) {
+ Slog.e(TAG, "Unexpected file name format", e);
+ }
+ }
+ return INVALID_UID;
+ }
+
/**
* Runnable that writes {@link #mJobSet} out to xml.
* NOTE: This Runnable locks on mLock
@@ -543,6 +565,42 @@
private void prepare() {
mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+ if (mUseSplitFiles) {
+ // Put the set of changed UIDs in the copy list so that we update each file,
+ // especially if we've dropped all jobs for that UID.
+ if (mPendingJobWriteUids.get(ALL_UIDS)) {
+ // ALL_UIDS is only used when we switch file splitting policy or for tests,
+ // so going through the file list here shouldn't be
+ // a large performance hit on user devices.
+
+ 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");
+ } else {
+ for (File file : files) {
+ final int uid = extractUidFromJobFileName(file);
+ if (uid != INVALID_UID) {
+ mJobStoreCopy.put(uid, new ArrayList<>());
+ }
+ }
+ }
+ } else {
+ for (int i = 0; i < mPendingJobWriteUids.size(); ++i) {
+ mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>());
+ }
+ }
+ } else {
+ // Single file mode.
+ // Put the catchall UID in the copy list so that we update the single file,
+ // especially if we've dropped all persisted jobs.
+ mJobStoreCopy.put(ALL_UIDS, new ArrayList<>());
+ }
}
@Override
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 da20684..af8e727 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
@@ -32,6 +32,7 @@
import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
import android.app.job.JobWorkItem;
import android.content.ClipData;
import android.content.ComponentName;
@@ -106,6 +107,13 @@
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
+ private static final int IMPLICIT_CONSTRAINTS = 0
+ | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
+ | CONSTRAINT_DEVICE_NOT_DOZING
+ | CONSTRAINT_FLEXIBLE
+ | CONSTRAINT_TARE_WEALTH
+ | CONSTRAINT_WITHIN_QUOTA;
+
// The following set of dynamic constraints are for specific use cases (as explained in their
// relative naming and comments). Right now, they apply different constraints, which is fine,
// but if in the future, we have overlapping dynamic constraint sets, removing one constraint
@@ -1613,6 +1621,101 @@
}
}
+ /**
+ * If {@link #isReady()} returns false, this will return a single reason why the job isn't
+ * ready. If {@link #isReady()} returns true, this will return
+ * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+ */
+ @JobScheduler.PendingJobReason
+ public int getPendingJobReason() {
+ final int unsatisfiedConstraints = ~satisfiedConstraints
+ & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+ if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
+ // The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
+ // the app is background restricted, or because we're restricting background work
+ // in battery saver. Assume that background restriction is the reason apps that
+ // jobs are not ready, and battery saver otherwise.
+ // This has the benefit of being consistent for background restricted apps
+ // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+ // battery saver state.
+ if (mIsUserBgRestricted) {
+ return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+ }
+ return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+ }
+ if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
+ if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+ if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
+ if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+ if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
+ }
+ if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
+ }
+ if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+ }
+ if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
+ }
+ if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
+ if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
+ // The developer requested this constraint, so it makes sense to return the
+ // explicit constraint reason.
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+ }
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+ if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+ }
+ if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+ }
+ if ((CONSTRAINT_TARE_WEALTH & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_QUOTA;
+ }
+ if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+ }
+ if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
+ return JobScheduler.PENDING_JOB_REASON_QUOTA;
+ }
+
+ if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+ Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+ // The user hasn't officially launched this app.
+ return JobScheduler.PENDING_JOB_REASON_USER;
+ }
+ if (serviceProcessName != null) {
+ return JobScheduler.PENDING_JOB_REASON_APP;
+ }
+
+ if (!isReady()) {
+ Slog.wtf(TAG, "Unknown reason job isn't ready");
+ }
+ return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ }
+
/** @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/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 4067541..7aab67a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -18,6 +18,7 @@
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
@@ -28,20 +29,23 @@
* Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
* should be scheduled or not based on the state of the system/device.
* Every restriction is associated with exactly one stop reason, which could be retrieved using
- * {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
+ * {@link #getStopReason()}, one pending reason (retrievable via {@link #getPendingReason()},
+ * (and the internal reason via {@link #getInternalReason()}).
* Note, that this is not taken into account for the jobs that have
* {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
*/
public abstract class JobRestriction {
final JobSchedulerService mService;
- private final int mReason;
+ private final int mStopReason;
+ private final int mPendingReason;
private final int mInternalReason;
- JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
- int internalReason) {
+ protected JobRestriction(JobSchedulerService service, @JobParameters.StopReason int stopReason,
+ @JobScheduler.PendingJobReason int pendingReason, int internalReason) {
mService = service;
- mReason = reason;
+ mPendingReason = pendingReason;
+ mStopReason = stopReason;
mInternalReason = internalReason;
}
@@ -70,10 +74,15 @@
public void dumpConstants(ProtoOutputStream proto) {
}
- /** @return reason code for the Restriction. */
+ @JobScheduler.PendingJobReason
+ public final int getPendingReason() {
+ return mPendingReason;
+ }
+
+ /** @return stop reason code for the Restriction. */
@JobParameters.StopReason
- public final int getReason() {
- return mReason;
+ public final int getStopReason() {
+ return mStopReason;
}
public final int getInternalReason() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index ca2fd60..830031e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -18,6 +18,7 @@
import android.app.job.JobInfo;
import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
import android.os.PowerManager;
import android.os.PowerManager.OnThermalStatusChangedListener;
import android.util.IndentingPrintWriter;
@@ -42,6 +43,7 @@
public ThermalStatusRestriction(JobSchedulerService service) {
super(service, JobParameters.STOP_REASON_DEVICE_STATE,
+ JobScheduler.PENDING_JOB_REASON_DEVICE_STATE,
JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
}
diff --git a/api/api.go b/api/api.go
index ba0fdc1..c91ff81 100644
--- a/api/api.go
+++ b/api/api.go
@@ -36,6 +36,8 @@
// built against module_current SDK). Instead they are directly statically
// linked into the all-framework-module-lib, which is building against hidden
// APIs.
+// In addition, the modules in this list are allowed to contribute to test APIs
+// stubs.
var non_updatable_modules = []string{virtualization}
// The intention behind this soong plugin is to generate a number of "merged"
@@ -246,9 +248,33 @@
}
func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
+ // First create the all-updatable-modules-system-stubs
+ {
+ updatable_modules := removeAll(modules, non_updatable_modules)
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-updatable-modules-system-stubs")
+ props.Static_libs = transformArray(updatable_modules, "", ".stubs.system")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+ // Now merge all-updatable-modules-system-stubs and stubs from non-updatable modules
+ // into all-modules-system-stubs.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-modules-system-stubs")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.system")
+ props.Static_libs = append(props.Static_libs, "all-updatable-modules-system-stubs")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+}
+
+func createMergedTestStubsForNonUpdatableModules(ctx android.LoadHookContext) {
props := libraryProps{}
- props.Name = proptools.StringPtr("all-modules-system-stubs")
- props.Static_libs = transformArray(modules, "", ".stubs.system")
+ props.Name = proptools.StringPtr("all-non-updatable-modules-test-stubs")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".stubs.test")
props.Sdk_version = proptools.StringPtr("module_current")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(java.LibraryFactory, &props)
@@ -360,6 +386,7 @@
createMergedPublicStubs(ctx, bootclasspath)
createMergedSystemStubs(ctx, bootclasspath)
+ createMergedTestStubsForNonUpdatableModules(ctx)
createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
createMergedFrameworkImpl(ctx, bootclasspath)
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index d8b348e..528ce86 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -9358,8 +9358,8 @@
android.widget.inline.InlinePresentationSpec$BaseBuilder
android.widget.inline.InlinePresentationSpec$Builder
android.widget.inline.InlinePresentationSpec
-android.window.BackEvent$1
-android.window.BackEvent
+android.window.BackMotionEvent$1
+android.window.BackMotionEvent
android.window.ClientWindowFrames$1
android.window.ClientWindowFrames
android.window.CompatOnBackInvokedCallback
diff --git a/config/preloaded-classes b/config/preloaded-classes
index f750249..fa60140 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -9389,8 +9389,8 @@
android.widget.inline.InlinePresentationSpec$BaseBuilder
android.widget.inline.InlinePresentationSpec$Builder
android.widget.inline.InlinePresentationSpec
-android.window.BackEvent$1
-android.window.BackEvent
+android.window.BackMotionEvent$1
+android.window.BackMotionEvent
android.window.ClientWindowFrames$1
android.window.ClientWindowFrames
android.window.CompatOnBackInvokedCallback
diff --git a/core/api/current.txt b/core/api/current.txt
index 614c4c3..434b60d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -94,6 +94,7 @@
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_FILE_MANAGEMENT = "android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT";
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";
@@ -8484,7 +8485,26 @@
method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
+ method public int getPendingJobReason(int);
method public abstract int schedule(@NonNull android.app.job.JobInfo);
+ field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
+ field public static final int PENDING_JOB_REASON_APP_STANDBY = 2; // 0x2
+ field public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3; // 0x3
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4; // 0x4
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
+ field public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11; // 0xb
+ field public static final int PENDING_JOB_REASON_DEVICE_STATE = 12; // 0xc
+ field public static final int PENDING_JOB_REASON_EXECUTING = -1; // 0xffffffff
+ field public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2; // 0xfffffffe
+ field public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13; // 0xd
+ field public static final int PENDING_JOB_REASON_QUOTA = 14; // 0xe
+ field public static final int PENDING_JOB_REASON_UNDEFINED = 0; // 0x0
+ field public static final int PENDING_JOB_REASON_USER = 15; // 0xf
field public static final int RESULT_FAILURE = 0; // 0x0
field public static final int RESULT_SUCCESS = 1; // 0x1
}
@@ -10346,6 +10366,7 @@
field @Deprecated @RequiresPermission("android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS") public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+ field public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
@@ -10594,6 +10615,7 @@
field public static final String EXTRA_UID = "android.intent.extra.UID";
field public static final String EXTRA_USER = "android.intent.extra.USER";
field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
+ field public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
field public static final int FILL_IN_ACTION = 1; // 0x1
field public static final int FILL_IN_CATEGORIES = 4; // 0x4
field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -11668,7 +11690,7 @@
public class PackageInstaller {
method public void abandonSession(int);
- method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
+ method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -12470,6 +12492,7 @@
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(android.Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT) public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 4096; // 0x1000
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
@@ -12478,10 +12501,10 @@
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(android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
- 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 @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
+ field @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
field public String permission;
}
@@ -20179,6 +20202,15 @@
}
+package android.location.altitude {
+
+ public final class AltitudeConverter {
+ ctor public AltitudeConverter();
+ method @WorkerThread public void addMslAltitude(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
+ }
+
+}
+
package android.location.provider {
public final class ProviderProperties implements android.os.Parcelable {
@@ -22017,6 +22049,11 @@
field public static final int AVCProfileHigh422 = 32; // 0x20
field public static final int AVCProfileHigh444 = 64; // 0x40
field public static final int AVCProfileMain = 2; // 0x2
+ field public static final int DTS_HDProfileHRA = 1; // 0x1
+ field public static final int DTS_HDProfileLBR = 2; // 0x2
+ field public static final int DTS_HDProfileMA = 4; // 0x4
+ field public static final int DTS_UHDProfileP1 = 1; // 0x1
+ field public static final int DTS_UHDProfileP2 = 2; // 0x2
field public static final int DolbyVisionLevel8k30 = 1024; // 0x400
field public static final int DolbyVisionLevel8k60 = 2048; // 0x800
field public static final int DolbyVisionLevelFhd24 = 4; // 0x4
@@ -35964,7 +36001,7 @@
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_APP_LONG_RUNNING_JOBS = "android.settings.MANAGE_APP_LONG_RUNNING_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";
@@ -38119,6 +38156,7 @@
field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+ field public static final int RETRY_AFTER_NEXT_REBOOT = 4; // 0x4
field public static final int RETRY_NEVER = 1; // 0x1
field public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3; // 0x3
field public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2; // 0x2
@@ -48751,7 +48789,7 @@
method public float getDesiredMaxAverageLuminance();
method public float getDesiredMaxLuminance();
method public float getDesiredMinLuminance();
- method public int[] getSupportedHdrTypes();
+ method @Deprecated public int[] getSupportedHdrTypes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.Display.HdrCapabilities> CREATOR;
field public static final int HDR_TYPE_DOLBY_VISION = 1; // 0x1
@@ -48768,6 +48806,7 @@
method public int getPhysicalHeight();
method public int getPhysicalWidth();
method public float getRefreshRate();
+ method @NonNull public int[] getSupportedHdrTypes();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
}
@@ -52704,7 +52743,7 @@
method public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
method public int getLiveRegion();
method public int getMaxTextLength();
- method public int getMinMillisBetweenContentChanges();
+ method @NonNull public java.time.Duration getMinDurationBetweenContentChanges();
method public int getMovementGranularities();
method public CharSequence getPackageName();
method @Nullable public CharSequence getPaneTitle();
@@ -52793,7 +52832,7 @@
method public void setLiveRegion(int);
method public void setLongClickable(boolean);
method public void setMaxTextLength(int);
- method public void setMinMillisBetweenContentChanges(int);
+ method public void setMinDurationBetweenContentChanges(@NonNull java.time.Duration);
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
method public void setPackageName(CharSequence);
@@ -59152,6 +59191,22 @@
package android.window {
+ public final class BackEvent {
+ ctor public BackEvent(float, float, float, int);
+ method @FloatRange(from=0, to=1) public float getProgress();
+ method public int getSwipeEdge();
+ method public float getTouchX();
+ method public float getTouchY();
+ field public static final int EDGE_LEFT = 0; // 0x0
+ field public static final int EDGE_RIGHT = 1; // 0x1
+ }
+
+ public interface OnBackAnimationCallback extends android.window.OnBackInvokedCallback {
+ method public default void onBackCancelled();
+ method public default void onBackProgressed(@NonNull android.window.BackEvent);
+ method public default void onBackStarted(@NonNull android.window.BackEvent);
+ }
+
public interface OnBackInvokedCallback {
method public void onBackInvoked();
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 286a800..3efb0c6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -419,10 +419,58 @@
}
public final class DeviceConfig {
+ field public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
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_ARC_APP_COMPAT = "arc_app_compat";
+ field public static final String NAMESPACE_CONFIGURATION = "configuration";
+ field public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER = "connectivity_thermal_power_manager";
+ field public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
+ field public static final String NAMESPACE_DEVICE_POLICY_MANAGER = "device_policy_manager";
+ field public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
+ field public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS = "intelligence_content_suggestions";
+ field public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
+ field public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
+ field public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
+ field public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
+ field public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE = "remote_key_provisioning_native";
+ field public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
+ field public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
+ field public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
+ field public static final String NAMESPACE_TARE = "tare";
+ field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
+ field public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
+ field public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = "virtualization_framework_native";
+ field public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
+ field public static final String NAMESPACE_WEAR = "wear";
+ field public static final String NAMESPACE_WIDGET = "widget";
+ field public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
+ }
+
+ public static class DeviceConfig.Properties {
+ ctor public DeviceConfig.Properties(@NonNull String, @Nullable java.util.Map<java.lang.String,java.lang.String>);
+ }
+
+ public final class Settings {
+ field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
+ field public static final int RESET_MODE_TRUSTED_DEFAULTS = 4; // 0x4
+ field public static final int RESET_MODE_UNTRUSTED_CHANGES = 3; // 0x3
+ field public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2; // 0x2
+ }
+
+ public static final class Settings.Config extends android.provider.Settings.NameValueTable {
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteString(@NonNull String, @NonNull String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static java.util.Map<java.lang.String,java.lang.String> getStrings(@NonNull String, @NonNull java.util.List<java.lang.String>);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean putString(@NonNull String, @NonNull String, @Nullable String, boolean);
+ method public static void registerContentObserver(@Nullable String, boolean, @NonNull android.database.ContentObserver);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setStrings(@NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>) throws android.provider.DeviceConfig.BadConfigException;
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
+ method public static void unregisterContentObserver(@NonNull android.database.ContentObserver);
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3817202..2e2ee23 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2984,13 +2984,17 @@
public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
+ method @NonNull public android.content.Context createContext();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.companion.virtual.audio.VirtualAudioDevice createVirtualAudioDevice(@NonNull android.hardware.display.VirtualDisplay, @Nullable java.util.concurrent.Executor, @Nullable android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback);
method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull java.util.List<java.lang.String>, @Nullable android.view.Surface, int, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.display.VirtualDisplay.Callback);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualDpad createVirtualDpad(@NonNull android.hardware.input.VirtualDpadConfig);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.input.VirtualKeyboardConfig);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualKeyboard createVirtualKeyboard(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.input.VirtualMouseConfig);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.input.VirtualTouchscreenConfig);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
@@ -3006,6 +3010,7 @@
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
method public int getDefaultActivityPolicy();
method public int getDefaultNavigationPolicy();
+ method public int getDefaultRecentsPolicy();
method public int getDevicePolicy(int);
method public int getLockState();
method @Nullable public String getName();
@@ -3022,6 +3027,7 @@
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
+ field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
}
public static final class VirtualDeviceParams.Builder {
@@ -3032,6 +3038,7 @@
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
@@ -4637,6 +4644,34 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
}
+ public final class VirtualDpadConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualDpadConfig> CREATOR;
+ }
+
+ public static final class VirtualDpadConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualDpadConfig.Builder> {
+ ctor public VirtualDpadConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualDpadConfig build();
+ }
+
+ public abstract class VirtualInputDeviceConfig {
+ ctor protected VirtualInputDeviceConfig(@NonNull android.hardware.input.VirtualInputDeviceConfig.Builder<? extends android.hardware.input.VirtualInputDeviceConfig.Builder<?>>);
+ ctor protected VirtualInputDeviceConfig(@NonNull android.os.Parcel);
+ method public int getAssociatedDisplayId();
+ method @NonNull public String getInputDeviceName();
+ method public int getProductId();
+ method public int getVendorId();
+ }
+
+ public abstract static class VirtualInputDeviceConfig.Builder<T extends android.hardware.input.VirtualInputDeviceConfig.Builder<T>> {
+ ctor public VirtualInputDeviceConfig.Builder();
+ method @NonNull public T setAssociatedDisplayId(int);
+ method @NonNull public T setInputDeviceName(@NonNull String);
+ method @NonNull public T setProductId(int);
+ method @NonNull public T setVendorId(int);
+ }
+
public final class VirtualKeyEvent implements android.os.Parcelable {
method public int describeContents();
method public int getAction();
@@ -4659,6 +4694,17 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendKeyEvent(@NonNull android.hardware.input.VirtualKeyEvent);
}
+ public final class VirtualKeyboardConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualKeyboardConfig> CREATOR;
+ }
+
+ public static final class VirtualKeyboardConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualKeyboardConfig.Builder> {
+ ctor public VirtualKeyboardConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualKeyboardConfig build();
+ }
+
public class VirtualMouse implements java.io.Closeable {
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition();
@@ -4689,6 +4735,17 @@
method @NonNull public android.hardware.input.VirtualMouseButtonEvent.Builder setButtonCode(int);
}
+ public final class VirtualMouseConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualMouseConfig> CREATOR;
+ }
+
+ public static final class VirtualMouseConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualMouseConfig.Builder> {
+ ctor public VirtualMouseConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualMouseConfig build();
+ }
+
public final class VirtualMouseRelativeEvent implements android.os.Parcelable {
method public int describeContents();
method public float getRelativeX();
@@ -4755,6 +4812,21 @@
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendTouchEvent(@NonNull android.hardware.input.VirtualTouchEvent);
}
+ public final class VirtualTouchscreenConfig extends android.hardware.input.VirtualInputDeviceConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getHeightInPixels();
+ method public int getWidthInPixels();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.VirtualTouchscreenConfig> CREATOR;
+ }
+
+ public static final class VirtualTouchscreenConfig.Builder extends android.hardware.input.VirtualInputDeviceConfig.Builder<android.hardware.input.VirtualTouchscreenConfig.Builder> {
+ ctor public VirtualTouchscreenConfig.Builder();
+ method @NonNull public android.hardware.input.VirtualTouchscreenConfig build();
+ method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setHeightInPixels(int);
+ method @NonNull public android.hardware.input.VirtualTouchscreenConfig.Builder setWidthInPixels(int);
+ }
+
}
package android.hardware.lights {
@@ -9350,6 +9422,7 @@
}
public class WifiNl80211Manager {
+ ctor public WifiNl80211Manager(@NonNull android.content.Context, @NonNull android.os.IBinder);
method public void abortScan(@NonNull String);
method public void enableVerboseLogging(boolean);
method @NonNull public int[] getChannelsMhzForBand(int);
@@ -10634,7 +10707,7 @@
}
public final class DeviceConfig {
- method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
+ method public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
@@ -10642,17 +10715,22 @@
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
+ method @NonNull public static java.util.List<java.lang.String> getPublicNamespaces();
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
+ method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
field public static final String NAMESPACE_ADSERVICES = "adservices";
field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service";
+ field public static final String NAMESPACE_ANDROID = "android";
field public static final String NAMESPACE_APPSEARCH = "appsearch";
field public static final String NAMESPACE_APP_COMPAT = "app_compat";
+ field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
field public static final String NAMESPACE_AUTOFILL = "autofill";
@@ -10664,13 +10742,16 @@
field public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
field public static final String NAMESPACE_CLIPBOARD = "clipboard";
field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
+ field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
field public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
+ field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
+ field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
field public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
field public static final String NAMESPACE_LOCATION = "location";
field public static final String NAMESPACE_MEDIA = "media";
@@ -10692,6 +10773,7 @@
field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
field public static final String NAMESPACE_SCHEDULER = "scheduler";
field public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
+ field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
field public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 25c4652..43d4530 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1511,13 +1511,33 @@
method public static boolean isEncodingLinearPcm(int);
}
+ public final class AudioHalVersionInfo implements java.lang.Comparable<android.media.AudioHalVersionInfo> android.os.Parcelable {
+ method public int compareTo(@NonNull android.media.AudioHalVersionInfo);
+ method public int describeContents();
+ method public int getHalType();
+ method public int getMajorVersion();
+ method public int getMinorVersion();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.media.AudioHalVersionInfo AIDL_1_0;
+ field public static final int AUDIO_HAL_TYPE_AIDL = 1; // 0x1
+ field public static final int AUDIO_HAL_TYPE_HIDL = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioHalVersionInfo> CREATOR;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_2_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_4_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_5_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_6_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_0;
+ field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_1;
+ field @NonNull public static final java.util.List<android.media.AudioHalVersionInfo> VERSIONS;
+ }
+
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
- method @Nullable public static String getHalVersion();
+ method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
method public static final int[] getPublicStreamTypes();
method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
method public int getStreamMinVolumeInt(int);
@@ -1719,7 +1739,6 @@
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;
}
@@ -2191,17 +2210,6 @@
field public static final android.net.Uri CORP_CONTENT_URI;
}
- public final class DeviceConfig {
- field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
- field public static final String NAMESPACE_ANDROID = "android";
- field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
- field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
- field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
- field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
- field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
- field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
- }
-
public interface InputMethodManagerDeviceConfig {
field public static final String KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS = "hide_ime_when_no_editor_focus";
}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a4a12d7..128e3de 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -424,6 +424,7 @@
"android/os/IInterface.java",
"android/os/Binder.java",
"android/os/IBinder.java",
+ "android/os/Parcelable.java",
],
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2dccd4d..31cbe28 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6717,6 +6717,13 @@
ii = null;
}
+ final IActivityManager mgr = ActivityManager.getService();
+ try {
+ mgr.finishAttachApplication(mStartSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
mConfigurationController.updateLocaleListFromAppContext(appContext);
@@ -6785,13 +6792,6 @@
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
- final IActivityManager mgr = ActivityManager.getService();
- try {
- mgr.finishAttachApplication(mStartSeq);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
-
// Wait for debugger after we have notified the system to finish attach application
if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
// XXX should have option to change the port.
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 332aadd..e99e360 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -23,6 +23,7 @@
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_FILE_MANAGEMENT;
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_MANIFEST;
@@ -412,6 +413,22 @@
);
/**
+ * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT}.
+ *
+ * @hide
+ */
+ public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_FILE_MANAGEMENT =
+ new ForegroundServiceTypePolicyInfo(
+ FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+ new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+ new RegularPermission(Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT)
+ }, true),
+ null
+ );
+
+ /**
* The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
*
* @hide
@@ -1057,6 +1074,8 @@
FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
FGS_TYPE_POLICY_SHORT_SERVICE);
+ mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ FGS_TYPE_POLICY_FILE_MANAGEMENT);
mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
FGS_TYPE_POLICY_SPECIAL_USE);
}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 0837d85..5c47ea2 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -24,11 +24,15 @@
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.graphics.Point;
import android.graphics.PointF;
+import android.hardware.input.VirtualDpadConfig;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.os.ResultReceiver;
/**
@@ -64,32 +68,22 @@
IAudioConfigChangedCallback configChangedCallback);
void onAudioSessionEnded();
-
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualDpad(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
+ in VirtualDpadConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualKeyboard(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
+ in VirtualKeyboardConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualMouse(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
+ in VirtualMouseConfig config,
IBinder token);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualTouchscreen(
- int displayId,
- String inputDeviceName,
- int vendorId,
- int productId,
- IBinder token,
- in Point screenSize);
+ in VirtualTouchscreenConfig config,
+ IBinder token);
void unregisterInputDevice(IBinder token);
int getInputDeviceId(IBinder token);
boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 568185a..0e6cfb1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -41,9 +41,13 @@
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.input.VirtualDpad;
+import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualTouchscreen;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -71,7 +75,6 @@
@SystemService(Context.VIRTUAL_DEVICE_SERVICE)
public final class VirtualDeviceManager {
- private static final boolean DEBUG = false;
private static final String TAG = "VirtualDeviceManager";
private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
@@ -80,7 +83,6 @@
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
| DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
| DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
/**
@@ -310,6 +312,18 @@
}
/**
+ * @return A new Context bound to this device. This is a convenience method equivalent to
+ * calling {@link Context#createDeviceContext(int)} with the device id of this device.
+ */
+ public @NonNull Context createContext() {
+ try {
+ return mContext.createDeviceContext(mVirtualDevice.getDeviceId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns this device's sensor with the given type and name, if any.
*
* @see VirtualDeviceParams.Builder#addVirtualSensorConfig
@@ -500,23 +514,15 @@
/**
* Creates a virtual dpad.
*
- * @param display the display that the events inputted through this device should target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param config the configurations of the virtual Dpad.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualDpad createVirtualDpad(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ public VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualDpad:" + inputDeviceName);
- mVirtualDevice.createVirtualDpad(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token);
+ "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualDpad(config, token);
return new VirtualDpad(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -526,23 +532,15 @@
/**
* Creates a virtual keyboard.
*
- * @param display the display that the events inputted through this device should target
- * @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param config the configurations of the virtual keyboard.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualKeyboard createVirtualKeyboard(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualKeyboard:" + inputDeviceName);
- mVirtualDevice.createVirtualKeyboard(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token);
+ "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualKeyboard(config, token);
return new VirtualKeyboard(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -550,26 +548,90 @@
}
/**
+ * Creates a virtual keyboard.
+ *
+ * @param display the display that the events inputted through this device should
+ * target
+ * @param inputDeviceName the name to call this input device
+ * @param vendorId the PCI vendor id
+ * @param productId the product id, as defined by the vendor
+ * @see #createVirtualKeyboard(VirtualKeyboardConfig config)
+ * @deprecated Use {@link #createVirtualKeyboard(VirtualKeyboardConfig config)} instead
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualKeyboard createVirtualKeyboard(@NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName, int vendorId, int productId) {
+ VirtualKeyboardConfig keyboardConfig =
+ new VirtualKeyboardConfig.Builder()
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setInputDeviceName(inputDeviceName)
+ .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+ .build();
+ return createVirtualKeyboard(keyboardConfig);
+ }
+
+ /**
+ * Creates a virtual mouse.
+ *
+ * @param config the configurations of the virtual mouse.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualMouse(config, token);
+ return new VirtualMouse(mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Creates a virtual mouse.
*
- * @param display the display that the events inputted through this device should target
+ * @param display the display that the events inputted through this device should
+ * target
* @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param vendorId the PCI vendor id
+ * @param productId the product id, as defined by the vendor
+ * @see #createVirtualMouse(VirtualMouseConfig config)
+ * @deprecated Use {@link #createVirtualMouse(VirtualMouseConfig config)} instead
+ * *
+ */
+ @Deprecated
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualMouse createVirtualMouse(@NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName, int vendorId, int productId) {
+ VirtualMouseConfig mouseConfig =
+ new VirtualMouseConfig.Builder()
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setInputDeviceName(inputDeviceName)
+ .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+ .build();
+ return createVirtualMouse(mouseConfig);
+ }
+
+ /**
+ * Creates a virtual touchscreen.
+ *
+ * @param config the configurations of the virtual touchscreen.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualMouse createVirtualMouse(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
+ public VirtualTouchscreen createVirtualTouchscreen(
+ @NonNull VirtualTouchscreenConfig config) {
try {
final IBinder token = new Binder(
- "android.hardware.input.VirtualMouse:" + inputDeviceName);
- mVirtualDevice.createVirtualMouse(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token);
- return new VirtualMouse(mVirtualDevice, token);
+ "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
+ mVirtualDevice.createVirtualTouchscreen(config, token);
+ return new VirtualTouchscreen(mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -578,29 +640,32 @@
/**
* Creates a virtual touchscreen.
*
- * @param display the display that the events inputted through this device should target
+ * @param display the display that the events inputted through this device should
+ * target
* @param inputDeviceName the name to call this input device
- * @param vendorId the PCI vendor id
- * @param productId the product id, as defined by the vendor
+ * @param vendorId the PCI vendor id
+ * @param productId the product id, as defined by the vendor
+ * @see #createVirtualTouchscreen(VirtualTouchscreenConfig config)
+ * @deprecated Use {@link #createVirtualTouchscreen(VirtualTouchscreenConfig config)}
+ * instead
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
- public VirtualTouchscreen createVirtualTouchscreen(
- @NonNull VirtualDisplay display,
- @NonNull String inputDeviceName,
- int vendorId,
- int productId) {
- try {
- final IBinder token = new Binder(
- "android.hardware.input.VirtualTouchscreen:" + inputDeviceName);
- final Point size = new Point();
- display.getDisplay().getSize(size);
- mVirtualDevice.createVirtualTouchscreen(display.getDisplay().getDisplayId(),
- inputDeviceName, vendorId, productId, token, size);
- return new VirtualTouchscreen(mVirtualDevice, token);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ public VirtualTouchscreen createVirtualTouchscreen(@NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName, int vendorId, int productId) {
+ final Point size = new Point();
+ display.getDisplay().getSize(size);
+ VirtualTouchscreenConfig touchscreenConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(vendorId)
+ .setProductId(productId)
+ .setInputDeviceName(inputDeviceName)
+ .setAssociatedDisplayId(display.getDisplay().getDisplayId())
+ .setWidthInPixels(size.x)
+ .setHeightInPixels(size.y)
+ .build();
+ return createVirtualTouchscreen(touchscreenConfig);
}
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 1cbe910..be77f2b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -147,6 +147,19 @@
*/
public static final int POLICY_TYPE_SENSORS = 0;
+ /** @hide */
+ @IntDef(flag = true, prefix = "RECENTS_POLICY_",
+ value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface RecentsPolicy {}
+
+ /**
+ * If set, activities launched on this virtual device are allowed to appear in the host device
+ * of the recently launched activities list.
+ */
+ public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1 << 0;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -161,6 +174,8 @@
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
+ @RecentsPolicy
+ private final int mDefaultRecentsPolicy;
private VirtualDeviceParams(
@LockState int lockState,
@@ -173,7 +188,8 @@
@ActivityPolicy int defaultActivityPolicy,
@Nullable String name,
@NonNull SparseIntArray devicePolicies,
- @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
+ @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
+ @RecentsPolicy int defaultRecentsPolicy) {
mLockState = lockState;
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -188,6 +204,7 @@
mName = name;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
+ mDefaultRecentsPolicy = defaultRecentsPolicy;
}
@SuppressWarnings("unchecked")
@@ -204,6 +221,7 @@
mDevicePolicies = parcel.readSparseIntArray();
mVirtualSensorConfigs = new ArrayList<>();
parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
+ mDefaultRecentsPolicy = parcel.readInt();
}
/**
@@ -328,6 +346,16 @@
return mVirtualSensorConfigs;
}
+ /**
+ * Returns the policy of how to handle activities in recents.
+ *
+ * @see RecentsPolicy
+ */
+ @RecentsPolicy
+ public int getDefaultRecentsPolicy() {
+ return mDefaultRecentsPolicy;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -346,6 +374,7 @@
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
+ dest.writeInt(mDefaultRecentsPolicy);
}
@Override
@@ -377,15 +406,17 @@
&& Objects.equals(mAllowedActivities, that.mAllowedActivities)
&& Objects.equals(mBlockedActivities, that.mBlockedActivities)
&& mDefaultActivityPolicy == that.mDefaultActivityPolicy
- && Objects.equals(mName, that.mName);
+ && Objects.equals(mName, that.mName)
+ && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
}
@Override
public int hashCode() {
int hashCode = Objects.hash(
mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
- mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
- mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+ mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
+ mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
+ mDefaultRecentsPolicy);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -407,6 +438,7 @@
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
+ " mName=" + mName
+ " mDevicePolicies=" + mDevicePolicies
+ + " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
+ ")";
}
@@ -442,6 +474,7 @@
@Nullable private String mName;
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
@NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+ private int mDefaultRecentsPolicy;
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -647,6 +680,17 @@
}
/**
+ * Sets the policy to indicate how activities are handled in recents.
+ *
+ * @param defaultRecentsPolicy A policy specifying how to handle activities in recents.
+ */
+ @NonNull
+ public Builder setDefaultRecentsPolicy(@RecentsPolicy int defaultRecentsPolicy) {
+ mDefaultRecentsPolicy = defaultRecentsPolicy;
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
*
* @throws IllegalArgumentException if there's mismatch between policy definition and
@@ -684,7 +728,8 @@
mDefaultActivityPolicy,
mName,
mDevicePolicies,
- mVirtualSensorConfigs);
+ mVirtualSensorConfigs,
+ mDefaultRecentsPolicy);
}
}
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 65b3ca4..86a672f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5107,6 +5107,21 @@
public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
/**
+ * Activity Action: Starts a note-taking activity that can be used to create a note. This action
+ * can be used to start an activity on the lock screen. Activity should ensure to appropriately
+ * handle privacy sensitive data and features when launched on the lock screen. See
+ * {@link android.app.KeyguardManager} for lock screen checks.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
+
+ /**
+ * A boolean extra used with {@link #ACTION_CREATE_NOTE} indicating whether the launched
+ * note-taking activity should show a UI that is suitable to use with stylus input.
+ */
+ public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
+
+ /**
* Broadcast Action: Sent to the integrity component when a package
* needs to be verified. The data contains the package URI along with other relevant
* information.
@@ -6109,6 +6124,14 @@
public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING";
/**
+ * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED}
+ * intents to indicate that this is a system update uninstall.
+ * @hide
+ */
+ public static final String EXTRA_SYSTEM_UPDATE_UNINSTALL =
+ "android.intent.extra.SYSTEM_UPDATE_UNINSTALL";
+
+ /**
* Used as an int extra field in {@link android.app.AlarmManager} pending intents
* to tell the application being invoked how many pending alarms are being
* delivered with the intent. For one-shot alarms this will always be 1.
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index c66f49c..a470de2 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -51,6 +51,7 @@
STATE_ENABLED_IMMUTABLE,
// @Deprecated STATE_TARGET_IS_BEING_REPLACED,
STATE_OVERLAY_IS_BEING_REPLACED,
+ STATE_SYSTEM_UPDATE_UNINSTALL,
})
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -128,6 +129,14 @@
public static final int STATE_ENABLED_IMMUTABLE = 6;
/**
+ * The target package needs to be refreshed as a result of a system update uninstall, which
+ * must recalculate the state of overlays against the newly enabled system package, which may
+ * differ in resources/policy from the /data variant that was uninstalled.
+ * @hide
+ */
+ public static final int STATE_SYSTEM_UPDATE_UNINSTALL = 7;
+
+ /**
* Overlay category: theme.
* <p>
* Change how Android (including the status bar, dialogs, ...) looks.
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c79f99d..1f01ae9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -867,10 +867,15 @@
*/
public void checkInstallConstraints(@NonNull List<String> packageNames,
@NonNull InstallConstraints constraints,
+ @NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<InstallConstraintsResult> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
try {
var remoteCallback = new RemoteCallback(b -> {
- callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+ executor.execute(() -> {
+ callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+ });
});
mInstaller.checkInstallConstraints(
mInstallerPackageName, packageNames, constraints, remoteCallback);
@@ -3675,7 +3680,7 @@
}
/**
- * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+ * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
*/
@DataClass(genParcelable = true, genHiddenConstructor = true)
public static final class InstallConstraintsResult implements Parcelable {
@@ -3783,7 +3788,7 @@
/**
* A class to encapsulate constraints for installation.
*
- * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+ * When used with {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, it
* specifies the conditions to check against for the packages in question. This can be used
* by app stores to deliver auto updates without disrupting the user experience (referred as
* gentle update) - for example, an app store might hold off updates when it find out the
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 7ea6733..fc2c532 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -331,8 +331,7 @@
* Messaging use cases which host local server to relay messages across devices.
*/
@RequiresPermission(
- value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING,
- conditional = true
+ value = Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING
)
public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 1 << 9;
@@ -360,8 +359,7 @@
* </p>
*/
@RequiresPermission(
- value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED,
- conditional = true
+ value = Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED
)
public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
@@ -412,6 +410,17 @@
public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
/**
+ * Constant corresponding to {@code fileManagement} in
+ * the {@link android.R.attr#foregroundServiceType} attribute.
+ * The file management use case which manages files/directories, often involving file I/O
+ * across the file system.
+ */
+ @RequiresPermission(
+ value = Manifest.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT
+ )
+ public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 1 << 12;
+
+ /**
* 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
@@ -457,8 +466,7 @@
* </pre>
*/
@RequiresPermission(
- value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE,
- conditional = true
+ value = Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE
)
public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1 << 30;
@@ -495,7 +503,8 @@
FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING,
FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
- FOREGROUND_SERVICE_TYPE_SPECIAL_USE
+ FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+ FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ForegroundServiceType {}
@@ -579,6 +588,8 @@
return "systemExempted";
case FOREGROUND_SERVICE_TYPE_SHORT_SERVICE:
return "shortService";
+ case FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT:
+ return "fileManagement";
case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
return "specialUse";
default:
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 1c1f58a..45e0efb 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -43,6 +43,36 @@
"name": "ApkVerityTest"
},
{
+ "name": "CtsAppFgsTestCases",
+ "file_patterns": ["(/|^)ServiceInfo[^/]*"],
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsShortFgsTestCases",
+ "file_patterns": ["(/|^)ServiceInfo[^/]*"],
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
"name": "CtsIncrementalInstallHostTestCases",
"options": [
{
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index f7675e8..23d108f 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -419,6 +419,15 @@
@TestApi
public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+ /**
+ * Virtual display flags: Indicates that the display should not be a part of the default
+ * DisplayGroup and instead be part of a DisplayGroup associated with its virtual device.
+ *
+ * @see #createVirtualDisplay
+ * @hide
+ */
+ public static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15;
+
/** @hide */
@IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 6f63dbf..a748b60 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -1014,8 +1014,22 @@
return;
}
- // TODO(b/218388821): Propagate all the parameters to FingerprintService.
- Slog.e(TAG, "onPointerDown: not implemented!");
+ final PointerContext pc = new PointerContext();
+ pc.pointerId = pointerId;
+ pc.x = x;
+ pc.y = y;
+ pc.minor = minor;
+ pc.major = major;
+ pc.orientation = orientation;
+ pc.time = time;
+ pc.gestureStart = gestureStart;
+ pc.isAod = isAod;
+
+ try {
+ mService.onPointerDown(requestId, sensorId, pc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
@@ -1040,8 +1054,22 @@
return;
}
- // TODO(b/218388821): Propagate all the parameters to FingerprintService.
- Slog.e(TAG, "onPointerUp: not implemented!");
+ final PointerContext pc = new PointerContext();
+ pc.pointerId = pointerId;
+ pc.x = x;
+ pc.y = y;
+ pc.minor = minor;
+ pc.major = major;
+ pc.orientation = orientation;
+ pc.time = time;
+ pc.gestureStart = gestureStart;
+ pc.isAod = isAod;
+
+ try {
+ mService.onPointerUp(requestId, sensorId, pc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bdcbcaa..b26c0a2 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -32,6 +32,8 @@
import android.os.IBinder;
import android.os.IVibratorStateListener;
import android.os.VibrationEffect;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
@@ -107,6 +109,20 @@
void removeKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier,
String keyboardLayoutDescriptor);
+ // New Keyboard layout config APIs
+ String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+
+ @EnforcePermission("SET_KEYBOARD_LAYOUT")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.SET_KEYBOARD_LAYOUT)")
+ void setKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype,
+ String keyboardLayoutDescriptor);
+
+ String[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier, int userId,
+ in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+
// Registers an input devices changed listener.
void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a157a8f..cea3fa1 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -26,6 +26,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
@@ -66,6 +67,8 @@
import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -889,6 +892,91 @@
}
/**
+ * Gets the keyboard layout descriptor for the specified input device, userId, imeInfo and
+ * imeSubtype.
+ *
+ * @param identifier Identifier for the input device
+ * @param userId user profile ID
+ * @param imeInfo contains IME information like imeId, etc.
+ * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+ * @return The keyboard layout descriptor, or null if no keyboard layout has been set.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ try {
+ return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the keyboard layout descriptor for the specified input device, userId, imeInfo and
+ * imeSubtype.
+ *
+ * <p>
+ * This method may have the side-effect of causing the input device in question to be
+ * reconfigured.
+ * </p>
+ *
+ * @param identifier The identifier for the input device.
+ * @param userId user profile ID
+ * @param imeInfo contains IME information like imeId, etc.
+ * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+ * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+ public void setKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype, @NonNull String keyboardLayoutDescriptor) {
+ if (identifier == null) {
+ throw new IllegalArgumentException("identifier must not be null");
+ }
+ if (keyboardLayoutDescriptor == null) {
+ throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+ }
+
+ try {
+ mIm.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype,
+ keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets all keyboard layout descriptors that are enabled for the specified input device, userId,
+ * imeInfo and imeSubtype.
+ *
+ * @param identifier The identifier for the input device.
+ * @param userId user profile ID
+ * @param imeInfo contains IME information like imeId, etc.
+ * @param imeSubtype contains IME subtype information like input languageTag, layoutType, etc.
+ * @return The keyboard layout descriptors.
+ *
+ * @hide
+ */
+ public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ if (identifier == null) {
+ throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+ }
+
+ try {
+ return mIm.getKeyboardLayoutListForInputDevice(identifier, userId, imeInfo, imeSubtype);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the mouse pointer speed.
* <p>
* Only returns the permanent mouse pointer speed. Ignores any temporary pointer
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/hardware/input/VirtualDpadConfig.aidl
similarity index 90%
copy from core/java/android/window/BackEvent.aidl
copy to core/java/android/hardware/input/VirtualDpadConfig.aidl
index 821f1fa..fac90f4 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/java/android/hardware/input/VirtualDpadConfig.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package android.hardware.input;
-/**
- * @hide
- */
-parcelable BackEvent;
+parcelable VirtualDpadConfig;
diff --git a/core/java/android/hardware/input/VirtualDpadConfig.java b/core/java/android/hardware/input/VirtualDpadConfig.java
new file mode 100644
index 0000000..d888dc0
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualDpadConfig.java
@@ -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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual Dpad.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualDpadConfig extends VirtualInputDeviceConfig implements Parcelable {
+ @NonNull
+ public static final Creator<VirtualDpadConfig> CREATOR = new Creator<VirtualDpadConfig>() {
+ @Override
+ public VirtualDpadConfig createFromParcel(Parcel in) {
+ return new VirtualDpadConfig(in);
+ }
+
+ @Override
+ public VirtualDpadConfig[] newArray(int size) {
+ return new VirtualDpadConfig[size];
+ }
+ };
+
+ private VirtualDpadConfig(@NonNull VirtualDpadConfig.Builder builder) {
+ super(builder);
+ }
+
+ private VirtualDpadConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for creating a {@link VirtualDpadConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+ /**
+ * Builds the {@link VirtualDpadConfig} instance.
+ */
+ @NonNull
+ public VirtualDpadConfig build() {
+ return new VirtualDpadConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
new file mode 100644
index 0000000..d3dacc9
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+
+/**
+ * Common configurations to create virtual input devices.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class VirtualInputDeviceConfig {
+ /** The vendor id uniquely identifies the company who manufactured the device. */
+ private final int mVendorId;
+ /**
+ * The product id uniquely identifies which product within the address space of a given vendor,
+ * identified by the device's vendor id.
+ */
+ private final int mProductId;
+ /** The associated display ID of the virtual input device. */
+ private final int mAssociatedDisplayId;
+ /** The name of the virtual input device. */
+ @NonNull
+ private final String mInputDeviceName;
+
+ protected VirtualInputDeviceConfig(@NonNull Builder<? extends Builder<?>> builder) {
+ mVendorId = builder.mVendorId;
+ mProductId = builder.mProductId;
+ mAssociatedDisplayId = builder.mAssociatedDisplayId;
+ mInputDeviceName = builder.mInputDeviceName;
+ }
+
+ protected VirtualInputDeviceConfig(@NonNull Parcel in) {
+ mVendorId = in.readInt();
+ mProductId = in.readInt();
+ mAssociatedDisplayId = in.readInt();
+ mInputDeviceName = in.readString8();
+ }
+
+ /**
+ * The vendor id uniquely identifies the company who manufactured the device.
+ */
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ /**
+ * The product id uniquely identifies which product within the address space of a given vendor,
+ * identified by the device's vendor id.
+ */
+ public int getProductId() {
+ return mProductId;
+ }
+
+ /**
+ * The associated display ID of the virtual input device.
+ */
+ public int getAssociatedDisplayId() {
+ return mAssociatedDisplayId;
+ }
+
+ /**
+ * The name of the virtual input device.
+ */
+ @NonNull
+ public String getInputDeviceName() {
+ return mInputDeviceName;
+ }
+
+ void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mVendorId);
+ dest.writeInt(mProductId);
+ dest.writeInt(mAssociatedDisplayId);
+ dest.writeString8(mInputDeviceName);
+ }
+
+ /**
+ * A builder for {@link VirtualInputDeviceConfig}
+ *
+ * @param <T> The subclass to be built.
+ */
+ @SuppressWarnings({"StaticFinalBuilder", "MissingBuildMethod"})
+ public abstract static class Builder<T extends Builder<T>> {
+
+ private int mVendorId;
+ private int mProductId;
+ private int mAssociatedDisplayId;
+ @NonNull
+ private String mInputDeviceName;
+
+ /** @see VirtualInputDeviceConfig#getVendorId(). */
+ @NonNull
+ public T setVendorId(int vendorId) {
+ mVendorId = vendorId;
+ return self();
+ }
+
+
+ /** @see VirtualInputDeviceConfig#getProductId(). */
+ @NonNull
+ public T setProductId(int productId) {
+ mProductId = productId;
+ return self();
+ }
+
+ /** @see VirtualInputDeviceConfig#getAssociatedDisplayId(). */
+ @NonNull
+ public T setAssociatedDisplayId(int displayId) {
+ mAssociatedDisplayId = displayId;
+ return self();
+ }
+
+ /** @see VirtualInputDeviceConfig#getInputDeviceName(). */
+ @NonNull
+ public T setInputDeviceName(@NonNull String deviceName) {
+ mInputDeviceName = deviceName;
+ return self();
+ }
+
+ /**
+ * Each subclass should return itself to allow the builder to chain properly
+ */
+ T self() {
+ return (T) this;
+ }
+ }
+}
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/hardware/input/VirtualKeyboardConfig.aidl
similarity index 89%
copy from core/java/android/window/BackEvent.aidl
copy to core/java/android/hardware/input/VirtualKeyboardConfig.aidl
index 821f1fa..8772e23 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package android.hardware.input;
-/**
- * @hide
- */
-parcelable BackEvent;
+parcelable VirtualKeyboardConfig;
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
new file mode 100644
index 0000000..9463857
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+
+/**
+ * Configurations to create virtual keyboard.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implements Parcelable {
+
+ @NonNull
+ public static final Creator<VirtualKeyboardConfig> CREATOR =
+ new Creator<VirtualKeyboardConfig>() {
+ @Override
+ public VirtualKeyboardConfig createFromParcel(Parcel in) {
+ return new VirtualKeyboardConfig(in);
+ }
+
+ @Override
+ public VirtualKeyboardConfig[] newArray(int size) {
+ return new VirtualKeyboardConfig[size];
+ }
+ };
+
+ private VirtualKeyboardConfig(@NonNull Builder builder) {
+ super(builder);
+ }
+
+ private VirtualKeyboardConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for creating a {@link VirtualKeyboardConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+ /**
+ * Builds the {@link VirtualKeyboardConfig} instance.
+ */
+ @NonNull
+ public VirtualKeyboardConfig build() {
+ return new VirtualKeyboardConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/hardware/input/VirtualMouseConfig.aidl
similarity index 90%
copy from core/java/android/window/BackEvent.aidl
copy to core/java/android/hardware/input/VirtualMouseConfig.aidl
index 821f1fa..a0d5fb5 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/java/android/hardware/input/VirtualMouseConfig.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package android.hardware.input;
-/**
- * @hide
- */
-parcelable BackEvent;
+parcelable VirtualMouseConfig;
diff --git a/core/java/android/hardware/input/VirtualMouseConfig.java b/core/java/android/hardware/input/VirtualMouseConfig.java
new file mode 100644
index 0000000..7ad5d04
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualMouseConfig.java
@@ -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 android.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual mouse.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualMouseConfig extends VirtualInputDeviceConfig implements Parcelable {
+ @NonNull
+ public static final Creator<VirtualMouseConfig> CREATOR = new Creator<VirtualMouseConfig>() {
+ @Override
+ public VirtualMouseConfig createFromParcel(Parcel in) {
+ return new VirtualMouseConfig(in);
+ }
+
+ @Override
+ public VirtualMouseConfig[] newArray(int size) {
+ return new VirtualMouseConfig[size];
+ }
+ };
+
+ private VirtualMouseConfig(@NonNull VirtualMouseConfig.Builder builder) {
+ super(builder);
+ }
+
+ private VirtualMouseConfig(@NonNull Parcel in) {
+ super(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Builder for creating a {@link VirtualMouseConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+
+ /**
+ * Builds the {@link VirtualMouseConfig} instance.
+ */
+ @NonNull
+ public VirtualMouseConfig build() {
+ return new VirtualMouseConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
similarity index 89%
copy from core/java/android/window/BackEvent.aidl
copy to core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
index 821f1fa..e4b0edb 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.aidl
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-package android.window;
+package android.hardware.input;
-/**
- * @hide
- */
-parcelable BackEvent;
+parcelable VirtualTouchscreenConfig;
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
new file mode 100644
index 0000000..e358619
--- /dev/null
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -0,0 +1,118 @@
+/*
+ * 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.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Configurations to create virtual touchscreen.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualTouchscreenConfig extends VirtualInputDeviceConfig implements Parcelable {
+
+ /** The touchscreen width in pixels. */
+ private final int mWidthInPixels;
+ /** The touchscreen height in pixels. */
+ private final int mHeightInPixels;
+
+ private VirtualTouchscreenConfig(@NonNull Builder builder) {
+ super(builder);
+ mWidthInPixels = builder.mWidthInPixels;
+ mHeightInPixels = builder.mHeightInPixels;
+ }
+
+ private VirtualTouchscreenConfig(@NonNull Parcel in) {
+ super(in);
+ mWidthInPixels = in.readInt();
+ mHeightInPixels = in.readInt();
+ }
+
+ /** Returns the touchscreen width in pixels. */
+ public int getWidthInPixels() {
+ return mWidthInPixels;
+ }
+
+ /** Returns the touchscreen height in pixels. */
+ public int getHeightInPixels() {
+ return mHeightInPixels;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mWidthInPixels);
+ dest.writeInt(mHeightInPixels);
+ }
+
+ @NonNull
+ public static final Creator<VirtualTouchscreenConfig> CREATOR =
+ new Creator<VirtualTouchscreenConfig>() {
+ @Override
+ public VirtualTouchscreenConfig createFromParcel(Parcel in) {
+ return new VirtualTouchscreenConfig(in);
+ }
+
+ @Override
+ public VirtualTouchscreenConfig[] newArray(int size) {
+ return new VirtualTouchscreenConfig[size];
+ }
+ };
+
+ /**
+ * Builder for creating a {@link VirtualTouchscreenConfig}.
+ */
+ public static final class Builder extends VirtualInputDeviceConfig.Builder<Builder> {
+ private int mWidthInPixels;
+ private int mHeightInPixels;
+
+ /**
+ * @see VirtualTouchscreenConfig#getWidthInPixels().
+ */
+ @NonNull
+ public Builder setWidthInPixels(int widthInPixels) {
+ mWidthInPixels = widthInPixels;
+ return this;
+ }
+
+ /**
+ * @see VirtualTouchscreenConfig#getHeightInPixels().
+ */
+ @NonNull
+ public Builder setHeightInPixels(int heightInPixels) {
+ mHeightInPixels = heightInPixels;
+ return this;
+ }
+
+ /**
+ * Builds the {@link VirtualTouchscreenConfig} instance.
+ */
+ @NonNull
+ public VirtualTouchscreenConfig build() {
+ return new VirtualTouchscreenConfig(this);
+ }
+ }
+}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 8278e89..def0cbd 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -355,6 +355,26 @@
}
/**
+ * Return the Linux UID assigned to the process that sent the transaction
+ * currently being processed.
+ *
+ * Logs WTF if the current thread is not currently
+ * executing an incoming transaction and the calling identity has not been
+ * explicitly set with {@link #clearCallingIdentity()}
+ *
+ * @hide
+ */
+ public static final int getCallingUidOrWtf() {
+ if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
+ Log.wtfStack(TAG,
+ "Thread is not in a binder transaction, "
+ + "and the calling identity has not been "
+ + "explicitly set with clearCallingIdentity");
+ }
+ return getCallingUid();
+ }
+
+ /**
* Return the UserHandle assigned to the process that sent you the
* current transaction that is being processed. This is the user
* of the caller. It is distinct from {@link #getCallingUid()} in that a
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 44a1fa5..249f486 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1446,28 +1446,6 @@
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/UserManager.java b/core/java/android/os/UserManager.java
index dd02e02..07c4b44 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -59,7 +59,7 @@
import android.graphics.drawable.Drawable;
import android.location.LocationManager;
import android.provider.Settings;
-import android.telephony.TelephonyManager;
+import android.telecom.TelecomManager;
import android.util.AndroidException;
import android.util.ArraySet;
import android.util.Log;
@@ -2139,13 +2139,8 @@
mContext.getContentResolver(),
Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
- boolean inCall = false;
- TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
- if (telephonyManager != null) {
- inCall = telephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE;
- }
boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
- return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall
+ return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall()
&& !isUserSwitchDisallowed;
}
@@ -2184,11 +2179,8 @@
android.Manifest.permission.MANAGE_USERS,
android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
- final TelephonyManager tm =
- (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-
int flags = SWITCHABILITY_STATUS_OK;
- if (tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+ if (inCall()) {
flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
}
if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
@@ -5622,6 +5614,11 @@
}
}
+ private boolean inCall() {
+ final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+ return telecomManager != null && telecomManager.isInCall();
+ }
+
/* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index ca88ae3..7df9290 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -23,9 +23,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.TestApi;
-import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.provider.Settings.Config.SyncDisabledMode;
@@ -55,13 +54,6 @@
@SystemApi
public final class DeviceConfig {
/**
- * The content:// style URL for the config table.
- *
- * @hide
- */
- public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
- /**
* Namespace for activity manager related features. These features will be applied
* immediately upon change.
*
@@ -75,6 +67,7 @@
* different namespace in order to avoid phonetype from resetting it.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
/**
@@ -93,7 +86,6 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- @TestApi
public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
/**
@@ -231,7 +223,6 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- @TestApi
public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
/**
@@ -291,6 +282,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
"intelligence_content_suggestions";
@@ -298,7 +290,7 @@
* Namespace for JobScheduler configurations.
* @hide
*/
- @TestApi
+ @SystemApi
public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
/**
@@ -338,6 +330,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
/**
@@ -393,6 +386,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE =
"remote_key_provisioning_native";
@@ -417,6 +411,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
/**
@@ -468,6 +463,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
/**
@@ -567,6 +563,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_TARE = "tare";
/**
@@ -591,6 +588,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
/**
@@ -598,6 +596,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
/**
@@ -608,7 +607,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final String NAMESPACE_ANDROID = "android";
/**
@@ -616,6 +615,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
/**
@@ -632,7 +632,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
/**
@@ -640,6 +640,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
/**
@@ -647,6 +648,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
"device_policy_manager";
@@ -697,6 +699,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_WIDGET = "widget";
/**
@@ -704,6 +707,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER =
"connectivity_thermal_power_manager";
@@ -712,6 +716,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_CONFIGURATION = "configuration";
/**
@@ -719,6 +724,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
/**
@@ -726,6 +732,8 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @SuppressLint("IntentName")
public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
/**
@@ -733,6 +741,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
/**
@@ -740,6 +749,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE =
"virtualization_framework_native";
@@ -748,7 +758,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
/**
@@ -756,7 +766,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
/**
@@ -790,6 +800,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
/**
@@ -797,6 +808,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
/**
@@ -804,6 +816,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
/**
@@ -811,6 +824,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_WEAR = "wear";
/**
@@ -826,7 +840,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
/**
@@ -842,6 +856,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
private static final Object sLock = new Object();
@@ -1119,6 +1134,7 @@
* @see #getSyncDisabledMode()
* @hide
*/
+ @SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
Settings.Config.setSyncDisabledMode(syncDisabledMode);
@@ -1130,6 +1146,7 @@
* @see #setSyncDisabledMode(int)
* @hide
*/
+ @SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static @SyncDisabledMode int getSyncDisabledMode() {
return Settings.Config.getSyncDisabledMode();
@@ -1150,12 +1167,10 @@
* @see #removeOnPropertiesChangedListener(OnPropertiesChangedListener)
*/
@SystemApi
- @RequiresPermission(READ_DEVICE_CONFIG)
public static void addOnPropertiesChangedListener(
@NonNull String namespace,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
- enforceReadPermission(namespace);
synchronized (sLock) {
Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
if (oldNamespace == null) {
@@ -1194,11 +1209,6 @@
}
}
- private static Uri createNamespaceUri(@NonNull String namespace) {
- Preconditions.checkNotNull(namespace);
- return CONTENT_URI.buildUpon().appendPath(namespace).build();
- }
-
/**
* Increment the count used to represent the number of listeners subscribed to the given
* namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
@@ -1223,7 +1233,7 @@
}
};
Settings.Config
- .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
+ .registerContentObserver(namespace, true, contentObserver);
sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
}
}
@@ -1283,23 +1293,10 @@
}
/**
- * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
- * @hide
- */
- 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:"
- + READ_DEVICE_CONFIG);
- }
- }
- }
-
- /**
* Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION;
* @hide
*/
+ @SystemApi
public static @NonNull List<String> getPublicNamespaces() {
return PUBLIC_NAMESPACES;
}
@@ -1356,6 +1353,7 @@
* @param keyValueMap A map between property names and property values.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
Preconditions.checkNotNull(namespace);
mNamespace = namespace;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 22d5a27..03846db 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -599,8 +599,8 @@
* 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";
+ public static final String ACTION_MANAGE_APP_LONG_RUNNING_JOBS =
+ "android.settings.MANAGE_APP_LONG_RUNNING_JOBS";
/**
* Activity Action: Show settings to allow configuration of cross-profile access for apps
@@ -2819,6 +2819,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@TestApi
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_PACKAGE_DEFAULTS = 1;
/**
@@ -2828,6 +2829,7 @@
* the setting will be deleted. This mode is only available to the system.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2;
/**
@@ -2838,6 +2840,7 @@
* This mode is only available to the system.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_UNTRUSTED_CHANGES = 3;
/**
@@ -2849,6 +2852,7 @@
* to the system.
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int RESET_MODE_TRUSTED_DEFAULTS = 4;
/** @hide */
@@ -3362,7 +3366,7 @@
public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
- DeviceConfig.enforceReadPermission(namespace);
+ Config.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
@@ -7224,6 +7228,15 @@
public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
/**
+ * Whether stylus button presses are disabled. This is a boolean that
+ * determines if stylus buttons are ignored.
+ *
+ * @hide
+ */
+ @SuppressLint("NoSettingsProvider")
+ public static final String STYLUS_BUTTONS_DISABLED = "stylus_buttons_disabled";
+
+ /**
* Host name and port for global http proxy. Uses ':' seperator for
* between host and port.
*
@@ -17992,6 +18005,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final class Config extends NameValueTable {
/**
@@ -18026,12 +18040,19 @@
*/
public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2;
+ /**
+ * The content:// style URL for the config table.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/config");
+
private static final ContentProviderHolder sProviderHolder =
- new ContentProviderHolder(DeviceConfig.CONTENT_URI);
+ new ContentProviderHolder(CONTENT_URI);
// Populated lazily, guarded by class object:
private static final NameValueCache sNameValueCache = new NameValueCache(
- DeviceConfig.CONTENT_URI,
+ CONTENT_URI,
CALL_METHOD_GET_CONFIG,
CALL_METHOD_PUT_CONFIG,
CALL_METHOD_DELETE_CONFIG,
@@ -18040,6 +18061,10 @@
sProviderHolder,
Config.class);
+ // Should never be invoked
+ private Config() {
+ }
+
/**
* Look up a name in the database.
* @param name to look up in the table
@@ -18047,8 +18072,10 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @Nullable
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- static String getString(String name) {
+ public static String getString(@NonNull String name) {
ContentResolver resolver = getContentResolver();
return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
}
@@ -18063,6 +18090,8 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @NonNull
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
public static Map<String, String> getStrings(@NonNull String namespace,
@NonNull List<String> names) {
@@ -18119,6 +18148,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
public static boolean putString(@NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
@@ -18138,6 +18168,7 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
public static boolean setStrings(@NonNull String namespace,
@NonNull Map<String, String> keyValues)
@@ -18188,8 +18219,9 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean deleteString(@NonNull String namespace,
+ public static boolean deleteString(@NonNull String namespace,
@NonNull String name) {
ContentResolver resolver = getContentResolver();
return sNameValueCache.deleteStringForUser(resolver,
@@ -18209,8 +18241,9 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void resetToDefaults(@ResetMode int resetMode,
+ public static void resetToDefaults(@ResetMode int resetMode,
@Nullable String namespace) {
try {
ContentResolver resolver = getContentResolver();
@@ -18224,7 +18257,7 @@
cp.call(resolver.getAttributionSource(),
sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg);
} catch (RemoteException e) {
- Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
+ Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
}
}
@@ -18234,9 +18267,10 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
+ public static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
try {
ContentResolver resolver = getContentResolver();
Bundle args = new Bundle();
@@ -18245,7 +18279,7 @@
cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(),
CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG, null, args);
} catch (RemoteException e) {
- Log.w(TAG, "Can't set sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+ Log.w(TAG, "Can't set sync disabled mode " + CONTENT_URI, e);
}
}
@@ -18255,9 +18289,10 @@
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static int getSyncDisabledMode() {
+ public static int getSyncDisabledMode() {
try {
ContentResolver resolver = getContentResolver();
Bundle args = Bundle.EMPTY;
@@ -18268,7 +18303,7 @@
null, args);
return bundle.getInt(KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN);
} catch (RemoteException e) {
- Log.w(TAG, "Can't query sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+ Log.w(TAG, "Can't query sync disabled mode " + CONTENT_URI, e);
}
return -1;
}
@@ -18288,21 +18323,26 @@
/**
- * Register a content observer
+ * Register a content observer.
*
* @hide
*/
- public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
- @NonNull ContentObserver observer) {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static void registerContentObserver(@Nullable String namespace,
+ boolean notifyForDescendants, @NonNull ContentObserver observer) {
ActivityThread.currentApplication().getContentResolver()
- .registerContentObserver(uri, notifyForDescendants, observer);
+ .registerContentObserver(createNamespaceUri(namespace),
+ notifyForDescendants, observer);
}
/**
- * Unregister a content observer
+ * Unregister a content observer.
+ * this may only be used with content observers registered through
+ * {@link Config#registerContentObserver}
*
* @hide
*/
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static void unregisterContentObserver(@NonNull ContentObserver observer) {
ActivityThread.currentApplication().getContentResolver()
.unregisterContentObserver(observer);
@@ -18330,6 +18370,21 @@
.getApplicationContext().checkCallingOrSelfPermission(permission);
}
+ /**
+ * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
+ * @hide
+ */
+ public static void enforceReadPermission(String namespace) {
+ if (ActivityThread.currentApplication().getApplicationContext()
+ .checkCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ != PackageManager.PERMISSION_GRANTED) {
+ if (!DeviceConfig.getPublicNamespaces().contains(namespace)) {
+ throw new SecurityException("Permission denial: reading from settings requires:"
+ + Manifest.permission.READ_DEVICE_CONFIG);
+ }
+ }
+ }
+
private static void registerMonitorCallbackAsUser(
@NonNull ContentResolver resolver, @UserIdInt int userHandle,
@NonNull RemoteCallback callback) {
@@ -18363,6 +18418,11 @@
return namespace + "/";
}
+ private static Uri createNamespaceUri(@NonNull String namespace) {
+ Preconditions.checkNotNull(namespace);
+ return CONTENT_URI.buildUpon().appendPath(namespace).build();
+ }
+
private static ContentResolver getContentResolver() {
return ActivityThread.currentApplication().getContentResolver();
}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e720f1a..4d6422c 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -126,6 +126,8 @@
Tag.BOOT_PATCHLEVEL; // KM_UINT | 719;
public static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION =
Tag.DEVICE_UNIQUE_ATTESTATION; // KM_BOOL | 720;
+ public static final int KM_TAG_ATTESTATION_ID_SECOND_IMEI =
+ Tag.ATTESTATION_ID_SECOND_IMEI; // KM_BYTES | 723;
public static final int KM_TAG_NONCE = Tag.NONCE; // KM_BYTES | 1001;
public static final int KM_TAG_MAC_LENGTH = Tag.MAC_LENGTH; // KM_UINT | 1003;
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index d9a310f..745f36d 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -332,7 +332,6 @@
}
@Override
- /** @hide */
protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("Service component: "); pw.println(
ComponentName.flattenToShortString(mServiceComponentName));
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 05c86172..cc83dec 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -525,9 +525,10 @@
return false;
}
+ // size() calls above took care about gc() compaction.
for (int index = 0; index < size; index++) {
- int key = keyAt(index);
- if (!Objects.equals(valueAt(index), other.get(key))) {
+ if (mKeys[index] != other.mKeys[index]
+ || !Objects.equals(mValues[index], other.mValues[index])) {
return false;
}
}
@@ -545,10 +546,11 @@
public int contentHashCode() {
int hash = 0;
int size = size();
+ // size() call above took care about gc() compaction.
for (int index = 0; index < size; index++) {
- int key = keyAt(index);
- E value = valueAt(index);
- hash = 31 * hash + Objects.hashCode(key);
+ int key = mKeys[index];
+ E value = (E) mValues[index];
+ hash = 31 * hash + key;
hash = 31 * hash + Objects.hashCode(value);
}
return hash;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 2745858..a42d3eb 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1942,13 +1942,16 @@
private final float mRefreshRate;
@NonNull
private final float[] mAlternativeRefreshRates;
+ @NonNull
+ @HdrCapabilities.HdrType
+ private final int[] mSupportedHdrTypes;
/**
* @hide
*/
@TestApi
public Mode(int width, int height, float refreshRate) {
- this(INVALID_MODE_ID, width, height, refreshRate, new float[0]);
+ this(INVALID_MODE_ID, width, height, refreshRate, new float[0], new int[0]);
}
/**
@@ -1956,14 +1959,14 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public Mode(int modeId, int width, int height, float refreshRate) {
- this(modeId, width, height, refreshRate, new float[0]);
+ this(modeId, width, height, refreshRate, new float[0], new int[0]);
}
/**
* @hide
*/
public Mode(int modeId, int width, int height, float refreshRate,
- float[] alternativeRefreshRates) {
+ float[] alternativeRefreshRates, @HdrCapabilities.HdrType int[] supportedHdrTypes) {
mModeId = modeId;
mWidth = width;
mHeight = height;
@@ -1971,6 +1974,8 @@
mAlternativeRefreshRates =
Arrays.copyOf(alternativeRefreshRates, alternativeRefreshRates.length);
Arrays.sort(mAlternativeRefreshRates);
+ mSupportedHdrTypes = Arrays.copyOf(supportedHdrTypes, supportedHdrTypes.length);
+ Arrays.sort(mSupportedHdrTypes);
}
/**
@@ -2045,6 +2050,15 @@
}
/**
+ * Returns the supported {@link HdrCapabilities} HDR_TYPE_* for this specific mode
+ */
+ @NonNull
+ @HdrCapabilities.HdrType
+ public int[] getSupportedHdrTypes() {
+ return mSupportedHdrTypes;
+ }
+
+ /**
* Returns {@code true} if this mode matches the given parameters.
*
* @hide
@@ -2118,7 +2132,8 @@
}
Mode that = (Mode) other;
return mModeId == that.mModeId && matches(that.mWidth, that.mHeight, that.mRefreshRate)
- && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates);
+ && Arrays.equals(mAlternativeRefreshRates, that.mAlternativeRefreshRates)
+ && Arrays.equals(mSupportedHdrTypes, that.mSupportedHdrTypes);
}
@Override
@@ -2129,6 +2144,7 @@
hash = hash * 17 + mHeight;
hash = hash * 17 + Float.floatToIntBits(mRefreshRate);
hash = hash * 17 + Arrays.hashCode(mAlternativeRefreshRates);
+ hash = hash * 17 + Arrays.hashCode(mSupportedHdrTypes);
return hash;
}
@@ -2141,6 +2157,8 @@
.append(", fps=").append(mRefreshRate)
.append(", alternativeRefreshRates=")
.append(Arrays.toString(mAlternativeRefreshRates))
+ .append(", supportedHdrTypes=")
+ .append(Arrays.toString(mSupportedHdrTypes))
.append("}")
.toString();
}
@@ -2151,7 +2169,8 @@
}
private Mode(Parcel in) {
- this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray());
+ this(in.readInt(), in.readInt(), in.readInt(), in.readFloat(), in.createFloatArray(),
+ in.createIntArray());
}
@Override
@@ -2161,6 +2180,7 @@
out.writeInt(mHeight);
out.writeFloat(mRefreshRate);
out.writeFloatArray(mAlternativeRefreshRates);
+ out.writeIntArray(mSupportedHdrTypes);
}
@SuppressWarnings("hiding")
@@ -2326,6 +2346,9 @@
/**
* Gets the supported HDR types of this display.
* Returns empty array if HDR is not supported by the display.
+ *
+ * @deprecated use {@link Display#getMode()}
+ * and {@link Mode#getSupportedHdrTypes()} instead
*/
public @HdrType int[] getSupportedHdrTypes() {
return mSupportedHdrTypes;
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 169c8c5..ce7606a0 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -227,8 +227,10 @@
* timebase.
* @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
* @param modeId The new mode Id
+ * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
*/
- public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+ public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
}
/**
@@ -303,8 +305,9 @@
// Called from native code.
@SuppressWarnings("unused")
- private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
- onModeChanged(timestampNanos, physicalDisplayId, modeId);
+ private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
+ onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
}
// Called from native code.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 138017c..85cb517 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -180,6 +180,16 @@
public int modeId;
/**
+ * The render frame rate this display is scheduled at, which is a divisor of the active mode
+ * refresh rate. This is the rate SurfaceFlinger would consume frames and would be observable
+ * by applications via the cadence of {@link android.view.Choreographer} callbacks and
+ * by backpressure when submitting buffers as fast as possible.
+ * Apps can call {@link android.view.Display#getRefreshRate} to query this value.
+ *
+ */
+ public float renderFrameRate;
+
+ /**
* The default display mode.
*/
public int defaultModeId;
@@ -376,6 +386,7 @@
&& Objects.equals(displayCutout, other.displayCutout)
&& rotation == other.rotation
&& modeId == other.modeId
+ && renderFrameRate == other.renderFrameRate
&& defaultModeId == other.defaultModeId
&& Arrays.equals(supportedModes, other.supportedModes)
&& colorMode == other.colorMode
@@ -428,6 +439,7 @@
displayCutout = other.displayCutout;
rotation = other.rotation;
modeId = other.modeId;
+ renderFrameRate = other.renderFrameRate;
defaultModeId = other.defaultModeId;
supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
colorMode = other.colorMode;
@@ -475,6 +487,7 @@
displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source);
rotation = source.readInt();
modeId = source.readInt();
+ renderFrameRate = source.readFloat();
defaultModeId = source.readInt();
int nModes = source.readInt();
supportedModes = new Display.Mode[nModes];
@@ -535,6 +548,7 @@
DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags);
dest.writeInt(rotation);
dest.writeInt(modeId);
+ dest.writeFloat(renderFrameRate);
dest.writeInt(defaultModeId);
dest.writeInt(supportedModes.length);
for (int i = 0; i < supportedModes.length; i++) {
@@ -764,6 +778,7 @@
sb.append(presentationDeadlineNanos);
sb.append(", mode ");
sb.append(modeId);
+ sb.append(renderFrameRate);
sb.append(", defaultMode ");
sb.append(defaultModeId);
sb.append(", modes ");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0743ccb..6d9f99f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,7 +53,6 @@
import android.view.KeyEvent;
import android.view.InputEvent;
import android.view.InsetsState;
-import android.view.InsetsVisibilities;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.InputChannel;
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index da54da16..58ee59d 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -31,6 +31,10 @@
*
* The insets frame will by default as the window frame size. If the providers are set, the
* calculation result based on the source size will be used as the insets frame.
+ *
+ * The InsetsFrameProvider should be self-contained. Nothing describing the window itself, such as
+ * contentInsets, visibleInsets, etc. won't affect the insets providing to other windows when this
+ * is set.
* @hide
*/
public class InsetsFrameProvider implements Parcelable {
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/core/java/android/view/InsetsVisibilities.aidl
deleted file mode 100644
index bd573ea..0000000
--- a/core/java/android/view/InsetsVisibilities.aidl
+++ /dev/null
@@ -1,19 +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 android.view;
-
-parcelable InsetsVisibilities;
diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java
deleted file mode 100644
index 7d259fb..0000000
--- a/core/java/android/view/InsetsVisibilities.java
+++ /dev/null
@@ -1,134 +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 android.view;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-import java.util.StringJoiner;
-
-/**
- * A collection of visibilities of insets. This is used for carrying the requested visibilities.
- * @hide
- */
-public class InsetsVisibilities implements Parcelable {
-
- private static final int UNSPECIFIED = 0;
- private static final int VISIBLE = 1;
- private static final int INVISIBLE = -1;
-
- private final int[] mVisibilities = new int[InsetsState.SIZE];
-
- public InsetsVisibilities() {
- }
-
- public InsetsVisibilities(InsetsVisibilities other) {
- set(other);
- }
-
- public InsetsVisibilities(Parcel in) {
- in.readIntArray(mVisibilities);
- }
-
- /**
- * Copies from another {@link InsetsVisibilities}.
- *
- * @param other an instance of {@link InsetsVisibilities}.
- */
- public void set(InsetsVisibilities other) {
- System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities,
- InsetsState.FIRST_TYPE, InsetsState.SIZE);
- }
-
- /**
- * Sets a visibility to a type.
- *
- * @param type The {@link @InsetsState.InternalInsetsType}.
- * @param visible {@code true} represents visible; {@code false} represents invisible.
- */
- public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) {
- mVisibilities[type] = visible ? VISIBLE : INVISIBLE;
- }
-
- /**
- * Returns the specified insets visibility of the type. If it has never been specified,
- * this returns the default visibility.
- *
- * @param type The {@link @InsetsState.InternalInsetsType}.
- * @return The specified visibility or the default one if it is not specified.
- */
- public boolean getVisibility(@InsetsState.InternalInsetsType int type) {
- final int visibility = mVisibilities[type];
- return visibility == UNSPECIFIED
- ? InsetsState.getDefaultVisibility(type)
- : visibility == VISIBLE;
- }
-
- @Override
- public String toString() {
- StringJoiner joiner = new StringJoiner(", ");
- for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) {
- final int visibility = mVisibilities[type];
- if (visibility != UNSPECIFIED) {
- joiner.add(InsetsState.typeToString(type) + ": "
- + (visibility == VISIBLE ? "visible" : "invisible"));
- }
- }
- return joiner.toString();
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mVisibilities);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof InsetsVisibilities)) {
- return false;
- }
- return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeIntArray(mVisibilities);
- }
-
- public void readFromParcel(@NonNull Parcel in) {
- in.readIntArray(mVisibilities);
- }
-
- public static final @NonNull Creator<InsetsVisibilities> CREATOR =
- new Creator<InsetsVisibilities>() {
-
- public InsetsVisibilities createFromParcel(Parcel in) {
- return new InsetsVisibilities(in);
- }
-
- public InsetsVisibilities[] newArray(int size) {
- return new InsetsVisibilities[size];
- }
- };
-}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e1ca0f1..89a0c05 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -1504,6 +1504,7 @@
public static final class DynamicDisplayInfo {
public DisplayMode[] supportedDisplayModes;
public int activeDisplayModeId;
+ public float renderFrameRate;
public int[] supportedColorModes;
public int activeColorMode;
@@ -1520,6 +1521,7 @@
return "DynamicDisplayInfo{"
+ "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
+ ", activeDisplayModeId=" + activeDisplayModeId
+ + ", renderFrameRate=" + renderFrameRate
+ ", supportedColorModes=" + Arrays.toString(supportedColorModes)
+ ", activeColorMode=" + activeColorMode
+ ", hdrCapabilities=" + hdrCapabilities
@@ -1535,6 +1537,7 @@
DynamicDisplayInfo that = (DynamicDisplayInfo) o;
return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
&& activeDisplayModeId == that.activeDisplayModeId
+ && renderFrameRate == that.renderFrameRate
&& Arrays.equals(supportedColorModes, that.supportedColorModes)
&& activeColorMode == that.activeColorMode
&& Objects.equals(hdrCapabilities, that.hdrCapabilities)
@@ -1544,7 +1547,7 @@
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
- activeColorMode, hdrCapabilities);
+ renderFrameRate, activeColorMode, hdrCapabilities);
}
}
@@ -1563,6 +1566,7 @@
public float refreshRate;
public long appVsyncOffsetNanos;
public long presentationDeadlineNanos;
+ public int[] supportedHdrTypes;
/**
* The config group ID this config is associated to.
@@ -1582,6 +1586,7 @@
+ ", refreshRate=" + refreshRate
+ ", appVsyncOffsetNanos=" + appVsyncOffsetNanos
+ ", presentationDeadlineNanos=" + presentationDeadlineNanos
+ + ", supportedHdrTypes=" + Arrays.toString(supportedHdrTypes)
+ ", group=" + group + "}";
}
@@ -1598,13 +1603,14 @@
&& Float.compare(that.refreshRate, refreshRate) == 0
&& appVsyncOffsetNanos == that.appVsyncOffsetNanos
&& presentationDeadlineNanos == that.presentationDeadlineNanos
+ && Arrays.equals(supportedHdrTypes, that.supportedHdrTypes)
&& group == that.group;
}
@Override
public int hashCode() {
return Objects.hash(id, width, height, xDpi, yDpi, refreshRate, appVsyncOffsetNanos,
- presentationDeadlineNanos, group);
+ presentationDeadlineNanos, group, Arrays.hashCode(supportedHdrTypes));
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 09a9d46..bfb489e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -77,6 +77,7 @@
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -505,6 +506,13 @@
Region mTouchableRegion;
Region mPreviousTouchableRegion;
+ private int mMeasuredWidth;
+ private int mMeasuredHeight;
+
+ // This indicates that we've already known the window size but without measuring the views.
+ // If this is true, we must measure the views before laying out them.
+ private boolean mViewMeasureDeferred;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int mWidth;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2593,7 +2601,8 @@
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
- final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+ final Resources res, final int desiredWindowWidth, final int desiredWindowHeight,
+ boolean forRootSizeOnly) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
@@ -2649,7 +2658,15 @@
lp.privateFlags);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
lp.privateFlags);
- performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ if (!forRootSizeOnly || !setMeasuredRootSizeFromSpec(
+ childWidthMeasureSpec, childHeightMeasureSpec)) {
+ performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+ } else {
+ // We already know how big the window should be before measuring the views.
+ // We can measure the views before laying out them. This is to avoid unnecessary
+ // measure.
+ mViewMeasureDeferred = true;
+ }
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
@@ -2665,6 +2682,25 @@
}
/**
+ * Sets the measured root size for requesting the window frame.
+ *
+ * @param widthMeasureSpec contains the size and the mode of the width.
+ * @param heightMeasureSpec contains the size and the mode of the height.
+ * @return {@code true} if we actually set the measured size; {@code false} otherwise.
+ */
+ private boolean setMeasuredRootSizeFromSpec(int widthMeasureSpec, int heightMeasureSpec) {
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+ // We don't know the exact size. We need to measure the hierarchy to know that.
+ return false;
+ }
+ mMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+ mMeasuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+ return true;
+ }
+
+ /**
* Modifies the input matrix such that it maps view-local coordinates to
* on-screen coordinates.
*
@@ -2751,6 +2787,14 @@
|| lp.type == TYPE_VOLUME_OVERLAY;
}
+ /**
+ * @return {@code true} if we should reduce unnecessary measure for the window.
+ * TODO(b/260382739): Apply this to all windows.
+ */
+ private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
+ return lp.type == TYPE_NOTIFICATION_SHADE;
+ }
+
private Rect getWindowBoundsInsetSystemBars() {
final Rect bounds = new Rect(
mContext.getResources().getConfiguration().windowConfiguration.getBounds());
@@ -2801,6 +2845,7 @@
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+ final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
WindowManager.LayoutParams params = null;
CompatibilityInfo compatibilityInfo =
@@ -2922,7 +2967,7 @@
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
- desiredWindowWidth, desiredWindowHeight);
+ desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
}
if (collectViewAttributes()) {
@@ -2962,8 +3007,8 @@
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
windowSizeMayChange |= measureHierarchy(host, lp,
- mView.getContext().getResources(),
- desiredWindowWidth, desiredWindowHeight);
+ mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
+ shouldOptimizeMeasure);
}
}
@@ -3383,6 +3428,13 @@
maybeHandleWindowMove(frame);
}
+ if (mViewMeasureDeferred) {
+ // It's time to measure the views since we are going to layout them.
+ performMeasure(
+ MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
+ }
+
if (!mRelayoutRequested && mCheckIfCanDraw) {
// We had a sync previously, but we didn't call IWindowSession#relayout in this
// traversal. So we don't know if the sync is complete that we can continue to draw.
@@ -3973,6 +4025,9 @@
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ mMeasuredWidth = mView.getMeasuredWidth();
+ mMeasuredHeight = mView.getMeasuredHeight();
+ mViewMeasureDeferred = false;
}
/**
@@ -4068,7 +4123,7 @@
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
- desiredWindowWidth, desiredWindowHeight);
+ desiredWindowWidth, desiredWindowHeight, false /* forRootSizeOnly */);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
@@ -8167,8 +8222,8 @@
final WindowConfiguration winConfigFromWm =
mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
final WindowConfiguration winConfig = getCompatWindowConfiguration();
- final int measuredWidth = mView.getMeasuredWidth();
- final int measuredHeight = mView.getMeasuredHeight();
+ final int measuredWidth = mMeasuredWidth;
+ final int measuredHeight = mMeasuredHeight;
final boolean relayoutAsync;
if (LOCAL_LAYOUT
&& (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 8de15c1..fd55d8d 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1493,37 +1493,37 @@
public static String toString(@InsetsType int types) {
StringBuilder result = new StringBuilder();
if ((types & STATUS_BARS) != 0) {
- result.append("statusBars |");
+ result.append("statusBars ");
}
if ((types & NAVIGATION_BARS) != 0) {
- result.append("navigationBars |");
+ result.append("navigationBars ");
}
if ((types & CAPTION_BAR) != 0) {
- result.append("captionBar |");
+ result.append("captionBar ");
}
if ((types & IME) != 0) {
- result.append("ime |");
+ result.append("ime ");
}
if ((types & SYSTEM_GESTURES) != 0) {
- result.append("systemGestures |");
+ result.append("systemGestures ");
}
if ((types & MANDATORY_SYSTEM_GESTURES) != 0) {
- result.append("mandatorySystemGestures |");
+ result.append("mandatorySystemGestures ");
}
if ((types & TAPPABLE_ELEMENT) != 0) {
- result.append("tappableElement |");
+ result.append("tappableElement ");
}
if ((types & DISPLAY_CUTOUT) != 0) {
- result.append("displayCutout |");
+ result.append("displayCutout ");
}
if ((types & WINDOW_DECOR) != 0) {
- result.append("windowDecor |");
+ result.append("windowDecor ");
}
if ((types & SYSTEM_OVERLAYS) != 0) {
- result.append("systemOverlays |");
+ result.append("systemOverlays ");
}
if (result.length() > 0) {
- result.delete(result.length() - 2, result.length());
+ result.delete(result.length() - 1, result.length());
}
return result.toString();
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index c067955..b91199d 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -67,6 +67,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -881,7 +882,7 @@
private long mTraversalBefore = UNDEFINED_NODE_ID;
private long mTraversalAfter = UNDEFINED_NODE_ID;
- private int mMinMillisBetweenContentChanges;
+ private long mMinDurationBetweenContentChanges = 0;
private int mBooleanProperties;
private final Rect mBoundsInParent = new Rect();
@@ -1797,18 +1798,21 @@
* </p>
*
* @see AccessibilityEvent#getContentChangeTypes for all content change types.
- * @param minMillisBetweenContentChanges the minimum duration between content change events.
+ * @param minDurationBetweenContentChanges the minimum duration between content change events.
+ * Negative duration would be treated as zero.
*/
- public void setMinMillisBetweenContentChanges(int minMillisBetweenContentChanges) {
+ public void setMinDurationBetweenContentChanges(
+ @NonNull Duration minDurationBetweenContentChanges) {
enforceNotSealed();
- mMinMillisBetweenContentChanges = minMillisBetweenContentChanges;
+ mMinDurationBetweenContentChanges = minDurationBetweenContentChanges.toMillis();
}
/**
* Gets the minimum time duration between two content change events.
*/
- public int getMinMillisBetweenContentChanges() {
- return mMinMillisBetweenContentChanges;
+ @NonNull
+ public Duration getMinDurationBetweenContentChanges() {
+ return Duration.ofMillis(mMinDurationBetweenContentChanges);
}
/**
@@ -4013,8 +4017,8 @@
fieldIndex++;
if (mTraversalAfter != DEFAULT.mTraversalAfter) nonDefaultFields |= bitAt(fieldIndex);
fieldIndex++;
- if (mMinMillisBetweenContentChanges
- != DEFAULT.mMinMillisBetweenContentChanges) {
+ if (mMinDurationBetweenContentChanges
+ != DEFAULT.mMinDurationBetweenContentChanges) {
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
@@ -4148,7 +4152,7 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalBefore);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeLong(mTraversalAfter);
if (isBitSet(nonDefaultFields, fieldIndex++)) {
- parcel.writeInt(mMinMillisBetweenContentChanges);
+ parcel.writeLong(mMinDurationBetweenContentChanges);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mConnectionId);
@@ -4305,7 +4309,7 @@
mLabeledById = other.mLabeledById;
mTraversalBefore = other.mTraversalBefore;
mTraversalAfter = other.mTraversalAfter;
- mMinMillisBetweenContentChanges = other.mMinMillisBetweenContentChanges;
+ mMinDurationBetweenContentChanges = other.mMinDurationBetweenContentChanges;
mWindowId = other.mWindowId;
mConnectionId = other.mConnectionId;
mUniqueId = other.mUniqueId;
@@ -4410,7 +4414,7 @@
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalBefore = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTraversalAfter = parcel.readLong();
if (isBitSet(nonDefaultFields, fieldIndex++)) {
- mMinMillisBetweenContentChanges = parcel.readInt();
+ mMinDurationBetweenContentChanges = parcel.readLong();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) mConnectionId = parcel.readInt();
@@ -4760,8 +4764,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);
+ builder.append("; minDurationBetweenContentChanges: ")
+ .append(mMinDurationBetweenContentChanges);
int granularities = mMovementGranularities;
builder.append("; MovementGranularities: [");
diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java
index 85b2881..40c0fee 100644
--- a/core/java/android/window/BackEvent.java
+++ b/core/java/android/window/BackEvent.java
@@ -16,29 +16,24 @@
package android.window;
+import android.annotation.FloatRange;
import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Represents an event that is sent out by the system during back navigation gesture.
- * Holds information about the touch event, swipe direction and overall progress of the gesture
- * interaction.
- *
- * @hide
+ * Object used to report back gesture progress.
+ * Holds information about the touch event, swipe direction and the animation progress that
+ * predictive back animations should seek to.
*/
-public class BackEvent implements Parcelable {
+public final class BackEvent {
/** Indicates that the edge swipe starts from the left edge of the screen */
public static final int EDGE_LEFT = 0;
/** Indicates that the edge swipe starts from the right edge of the screen */
public static final int EDGE_RIGHT = 1;
+ /** @hide */
@IntDef({
EDGE_LEFT,
EDGE_RIGHT,
@@ -52,78 +47,52 @@
@SwipeEdge
private final int mSwipeEdge;
- @Nullable
- private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
- * Creates a new {@link BackEvent} instance.
+ * Creates a new {@link BackMotionEvent} instance.
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
- * @param departingAnimationTarget The remote animation target of the departing
- * application window.
*/
- public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
- @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
- mDepartingAnimationTarget = departingAnimationTarget;
- }
-
- private BackEvent(@NonNull Parcel in) {
- mTouchX = in.readFloat();
- mTouchY = in.readFloat();
- mProgress = in.readFloat();
- mSwipeEdge = in.readInt();
- mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
- }
-
- public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
- @Override
- public BackEvent createFromParcel(Parcel in) {
- return new BackEvent(in);
- }
-
- @Override
- public BackEvent[] newArray(int size) {
- return new BackEvent[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeFloat(mTouchX);
- dest.writeFloat(mTouchY);
- dest.writeFloat(mProgress);
- dest.writeInt(mSwipeEdge);
- dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
- * Returns a value between 0 and 1 on how far along the back gesture is.
+ * Returns a value between 0 and 1 on how far along the back gesture is. This value is
+ * driven by the horizontal location of the touch point, and should be used as the fraction to
+ * seek the predictive back animation with. Specifically,
+ * <ol>
+ * <li>The progress is 0 when the touch is at the starting edge of the screen (left or right),
+ * and animation should seek to its start state.
+ * <li>The progress is approximately 1 when the touch is at the opposite side of the screen,
+ * and animation should seek to its end state. Exact end value may vary depending on
+ * screen size.
+ * </ol>
+ * In-between locations are linearly interpolated based on horizontal distance from the starting
+ * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold.
*/
+ @FloatRange(from = 0, to = 1)
public float getProgress() {
return mProgress;
}
/**
- * Returns the absolute X location of the touch point.
+ * Returns the absolute X location of the touch point, or NaN if the event is from
+ * a button press.
*/
public float getTouchX() {
return mTouchX;
}
/**
- * Returns the absolute Y location of the touch point.
+ * Returns the absolute Y location of the touch point, or NaN if the event is from
+ * a button press.
*/
public float getTouchY() {
return mTouchY;
@@ -132,20 +101,11 @@
/**
* Returns the screen edge that the swipe starts from.
*/
+ @SwipeEdge
public int getSwipeEdge() {
return mSwipeEdge;
}
- /**
- * Returns the {@link RemoteAnimationTarget} of the top departing application window,
- * or {@code null} if the top window should not be moved for the current type of back
- * destination.
- */
- @Nullable
- public RemoteAnimationTarget getDepartingAnimationTarget() {
- return mDepartingAnimationTarget;
- }
-
@Override
public String toString() {
return "BackEvent{"
diff --git a/core/java/android/window/BackEvent.aidl b/core/java/android/window/BackMotionEvent.aidl
similarity index 95%
rename from core/java/android/window/BackEvent.aidl
rename to core/java/android/window/BackMotionEvent.aidl
index 821f1fa..7c675c3 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/java/android/window/BackMotionEvent.aidl
@@ -19,4 +19,4 @@
/**
* @hide
*/
-parcelable BackEvent;
+parcelable BackMotionEvent;
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
new file mode 100644
index 0000000..8012a1c
--- /dev/null
+++ b/core/java/android/window/BackMotionEvent.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 android.window;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.RemoteAnimationTarget;
+
+/**
+ * Object used to report back gesture progress. Holds information about a {@link BackEvent} plus
+ * any {@link RemoteAnimationTarget} the gesture manipulates.
+ *
+ * @see BackEvent
+ * @hide
+ */
+public final class BackMotionEvent implements Parcelable {
+ private final float mTouchX;
+ private final float mTouchY;
+ private final float mProgress;
+
+ @BackEvent.SwipeEdge
+ private final int mSwipeEdge;
+ @Nullable
+ private final RemoteAnimationTarget mDepartingAnimationTarget;
+
+ /**
+ * Creates a new {@link BackMotionEvent} instance.
+ *
+ * @param touchX Absolute X location of the touch point of this event.
+ * @param touchY Absolute Y location of the touch point of this event.
+ * @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param swipeEdge Indicates which edge the swipe starts from.
+ * @param departingAnimationTarget The remote animation target of the departing
+ * application window.
+ */
+ public BackMotionEvent(float touchX, float touchY, float progress,
+ @BackEvent.SwipeEdge int swipeEdge,
+ @Nullable RemoteAnimationTarget departingAnimationTarget) {
+ mTouchX = touchX;
+ mTouchY = touchY;
+ mProgress = progress;
+ mSwipeEdge = swipeEdge;
+ mDepartingAnimationTarget = departingAnimationTarget;
+ }
+
+ private BackMotionEvent(@NonNull Parcel in) {
+ mTouchX = in.readFloat();
+ mTouchY = in.readFloat();
+ mProgress = in.readFloat();
+ mSwipeEdge = in.readInt();
+ mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
+ }
+
+ @NonNull
+ public static final Creator<BackMotionEvent> CREATOR = new Creator<BackMotionEvent>() {
+ @Override
+ public BackMotionEvent createFromParcel(Parcel in) {
+ return new BackMotionEvent(in);
+ }
+
+ @Override
+ public BackMotionEvent[] newArray(int size) {
+ return new BackMotionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeFloat(mTouchX);
+ dest.writeFloat(mTouchY);
+ dest.writeFloat(mProgress);
+ dest.writeInt(mSwipeEdge);
+ dest.writeTypedObject(mDepartingAnimationTarget, flags);
+ }
+
+ /**
+ * Returns the progress of a {@link BackEvent}.
+ *
+ * @see BackEvent#getProgress()
+ */
+ @FloatRange(from = 0, to = 1)
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Returns the absolute X location of the touch point.
+ */
+ public float getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public float getTouchY() {
+ return mTouchY;
+ }
+
+ /**
+ * Returns the screen edge that the swipe starts from.
+ */
+ @BackEvent.SwipeEdge
+ public int getSwipeEdge() {
+ return mSwipeEdge;
+ }
+
+ /**
+ * Returns the {@link RemoteAnimationTarget} of the top departing application window,
+ * or {@code null} if the top window should not be moved for the current type of back
+ * destination.
+ */
+ @Nullable
+ public RemoteAnimationTarget getDepartingAnimationTarget() {
+ return mDepartingAnimationTarget;
+ }
+
+ @Override
+ public String toString() {
+ return "BackMotionEvent{"
+ + "mTouchX=" + mTouchX
+ + ", mTouchY=" + mTouchY
+ + ", mProgress=" + mProgress
+ + ", mSwipeEdge" + mSwipeEdge
+ + ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ + "}";
+ }
+}
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index 2e3afde..14a57e0 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -40,7 +40,7 @@
private final SpringAnimation mSpring;
private ProgressCallback mCallback;
private float mProgress = 0;
- private BackEvent mLastBackEvent;
+ private BackMotionEvent mLastBackEvent;
private boolean mStarted = false;
private void setProgress(float progress) {
@@ -82,9 +82,9 @@
/**
* Sets a new target position for the back progress.
*
- * @param event the {@link BackEvent} containing the latest target progress.
+ * @param event the {@link BackMotionEvent} containing the latest target progress.
*/
- public void onBackProgressed(BackEvent event) {
+ public void onBackProgressed(BackMotionEvent event) {
if (!mStarted) {
return;
}
@@ -98,11 +98,11 @@
/**
* Starts the back progress animation.
*
- * @param event the {@link BackEvent} that started the gesture.
+ * @param event the {@link BackMotionEvent} that started the gesture.
* @param callback the back callback to invoke for the gesture. It will receive back progress
* dispatches as the progress animation updates.
*/
- public void onBackStarted(BackEvent event, ProgressCallback callback) {
+ public void onBackStarted(BackMotionEvent event, ProgressCallback callback) {
reset();
mLastBackEvent = event;
mCallback = callback;
@@ -132,8 +132,7 @@
}
mCallback.onProgressUpdate(
new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(),
- progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge(),
- mLastBackEvent.getDepartingAnimationTarget()));
+ progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge()));
}
}
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index f55932e..e027934 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -128,9 +128,10 @@
ActivityInfo activityInfo, int windowFlags, int systemWindowFlags);
/**
- * Returns {@code true} if the tasks which is on this virtual display can be showed on Recents.
+ * Returns {@code true} if the tasks which is on this virtual display can be showed in the
+ * host device of the recently launched activities list.
*/
- public abstract boolean canShowTasksInRecents();
+ public abstract boolean canShowTasksInHostDeviceRecents();
/**
* This is called when the top activity of the display is changed.
diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl
index 6af8ddd..159c0e8 100644
--- a/core/java/android/window/IOnBackInvokedCallback.aidl
+++ b/core/java/android/window/IOnBackInvokedCallback.aidl
@@ -17,7 +17,7 @@
package android.window;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager
@@ -30,18 +30,19 @@
* Called when a back gesture has been started, or back button has been pressed down.
* Wraps {@link OnBackInvokedCallback#onBackStarted(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the touch or button press.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the touch
+ * or button press.
*/
- void onBackStarted(in BackEvent backEvent);
+ void onBackStarted(in BackMotionEvent backMotionEvent);
/**
* Called on back gesture progress.
* Wraps {@link OnBackInvokedCallback#onBackProgressed(BackEvent)}.
*
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
+ * @param backMotionEvent The {@link BackMotionEvent} containing information about the latest
+ * touch point and the progress that the back animation should seek to.
*/
- void onBackProgressed(in BackEvent backEvent);
+ void onBackProgressed(in BackMotionEvent backMotionEvent);
/**
* Called when a back gesture or back button press has been cancelled.
diff --git a/core/java/android/window/OnBackAnimationCallback.java b/core/java/android/window/OnBackAnimationCallback.java
index c05809b..9119e71 100644
--- a/core/java/android/window/OnBackAnimationCallback.java
+++ b/core/java/android/window/OnBackAnimationCallback.java
@@ -18,6 +18,8 @@
import android.app.Activity;
import android.app.Dialog;
import android.view.View;
+import android.view.Window;
+
/**
* Interface for applications to register back animation callbacks along their custom back
* handling.
@@ -25,24 +27,29 @@
* This allows the client to customize various back behaviors by overriding the corresponding
* callback methods.
* <p>
- * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, held
- * by classes that implement {@link OnBackInvokedDispatcherOwner} (such as {@link Activity},
- * {@link Dialog} and {@link View}).
+ * Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
+ * is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
+ * {@link Dialog#getOnBackInvokedDispatcher()}, {@link Window#getOnBackInvokedDispatcher()}
+ * and {@link View#findOnBackInvokedDispatcher()}.
* <p>
* When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
* they are added within the same priority. Between different priorities, callbacks with higher
* priority are invoked first.
* <p>
* @see OnBackInvokedCallback
- * @hide
*/
public interface OnBackAnimationCallback extends OnBackInvokedCallback {
/**
* Called when a back gesture has been started, or back button has been pressed down.
+ *
+ * @param backEvent The {@link BackEvent} containing information about the touch or
+ * button press.
+ * @see BackEvent
*/
- default void onBackStarted() { }
+ default void onBackStarted(@NonNull BackEvent backEvent) {}
+
/**
- * Called on back gesture progress.
+ * Called when a back gesture progresses.
*
* @param backEvent An {@link BackEvent} object describing the progress event.
*
diff --git a/core/java/android/window/OnBackInvokedCallback.java b/core/java/android/window/OnBackInvokedCallback.java
index 62c41bf..6beaad3 100644
--- a/core/java/android/window/OnBackInvokedCallback.java
+++ b/core/java/android/window/OnBackInvokedCallback.java
@@ -16,9 +16,9 @@
package android.window;
-import android.annotation.NonNull;
import android.app.Activity;
import android.app.Dialog;
+import android.view.View;
import android.view.Window;
/**
@@ -26,7 +26,8 @@
* <p>
* Callback instances can be added to and removed from {@link OnBackInvokedDispatcher}, which
* is held at window level and accessible through {@link Activity#getOnBackInvokedDispatcher()},
- * {@link Dialog#getOnBackInvokedDispatcher()} and {@link Window#getOnBackInvokedDispatcher()}.
+ * {@link Dialog#getOnBackInvokedDispatcher()}, {@link Window#getOnBackInvokedDispatcher()}
+ * and {@link View#findOnBackInvokedDispatcher()}.
* <p>
* When back is triggered, callbacks on the in-focus window are invoked in reverse order in which
* they are added within the same priority. Between different priorities, callbacks with higher
@@ -35,6 +36,9 @@
* This replaces {@link Activity#onBackPressed()}, {@link Dialog#onBackPressed()} and
* {@link android.view.KeyEvent#KEYCODE_BACK}
* <p>
+ * If you want to customize back animation behaviors, in addition to handling back invocations,
+ * register its subclass instances {@link OnBackAnimationCallback} instead.
+ * <p>
* @see OnBackInvokedDispatcher#registerOnBackInvokedCallback(int, OnBackInvokedCallback)
* registerOnBackInvokedCallback(priority, OnBackInvokedCallback)
* to specify callback priority.
@@ -42,35 +46,8 @@
@SuppressWarnings("deprecation")
public interface OnBackInvokedCallback {
/**
- * Called when a back gesture has been started, or back button has been pressed down.
- *
- * @param backEvent The {@link BackEvent} containing information about the touch or
- * button press.
- *
- * @hide
- */
- default void onBackStarted(@NonNull BackEvent backEvent) {}
-
- /**
- * Called when a back gesture has been progressed.
- *
- * @param backEvent The {@link BackEvent} containing information about the latest touch point
- * and the progress that the back animation should seek to.
- *
- * @hide
- */
- default void onBackProgressed(@NonNull BackEvent backEvent) {}
-
- /**
* Called when a back gesture has been completed and committed, or back button pressed
* has been released and committed.
*/
void onBackInvoked();
-
- /**
- * Called when a back gesture or button press has been cancelled.
- *
- * @hide
- */
- default void onBackCancelled() {}
}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
new file mode 100644
index 0000000..1a58fd5
--- /dev/null
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -0,0 +1,508 @@
+/*
+ * 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.window;
+
+import static android.graphics.Color.WHITE;
+import static android.graphics.Color.alpha;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
+import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
+import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+
+import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
+import static com.android.internal.policy.DecorView.getNavigationBarRect;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.HardwareBuffer;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.DecorView;
+
+/**
+ * Utils class to help draw a snapshot on a surface.
+ * @hide
+ */
+public class SnapshotDrawerUtils {
+ private static final String TAG = "SnapshotDrawerUtils";
+
+ /**
+ * When creating the starting window, we use the exact same layout flags such that we end up
+ * with a window with the exact same dimensions etc. However, these flags are not used in layout
+ * and might cause other side effects so we exclude them.
+ */
+ static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
+ | FLAG_NOT_TOUCHABLE
+ | FLAG_NOT_TOUCH_MODAL
+ | FLAG_ALT_FOCUSABLE_IM
+ | FLAG_NOT_FOCUSABLE
+ | FLAG_HARDWARE_ACCELERATED
+ | FLAG_IGNORE_CHEEK_PRESSES
+ | FLAG_LOCAL_FOCUS_MODE
+ | FLAG_SLIPPERY
+ | FLAG_WATCH_OUTSIDE_TOUCH
+ | FLAG_SPLIT_TOUCH
+ | FLAG_SCALED
+ | FLAG_SECURE;
+
+ private static final RectF sTmpSnapshotSize = new RectF();
+ private static final RectF sTmpDstFrame = new RectF();
+
+ private static final Matrix sSnapshotMatrix = new Matrix();
+ private static final float[] sTmpFloat9 = new float[9];
+ private static final Paint sBackgroundPaint = new Paint();
+
+ /**
+ * The internal object to hold the surface and drawing on it.
+ */
+ @VisibleForTesting
+ public static class SnapshotSurface {
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private final SurfaceControl mRootSurface;
+ private final TaskSnapshot mSnapshot;
+ private final CharSequence mTitle;
+
+ private SystemBarBackgroundPainter mSystemBarBackgroundPainter;
+ private final Rect mTaskBounds;
+ private final Rect mFrame = new Rect();
+ private final Rect mSystemBarInsets = new Rect();
+ private boolean mSizeMismatch;
+
+ public SnapshotSurface(SurfaceControl rootSurface, TaskSnapshot snapshot,
+ CharSequence title,
+ Rect taskBounds) {
+ mRootSurface = rootSurface;
+ mSnapshot = snapshot;
+ mTitle = title;
+ mTaskBounds = taskBounds;
+ }
+
+ /**
+ * Initiate system bar painter to draw the system bar background.
+ */
+ void initiateSystemBarPainter(int windowFlags, int windowPrivateFlags,
+ int appearance, ActivityManager.TaskDescription taskDescription,
+ @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+ mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
+ windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
+ int backgroundColor = taskDescription.getBackgroundColor();
+ sBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
+ }
+
+ /**
+ * Set frame size.
+ */
+ void setFrames(Rect frame, Rect systemBarInsets) {
+ mFrame.set(frame);
+ mSystemBarInsets.set(systemBarInsets);
+ final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+ mSizeMismatch = (mFrame.width() != snapshot.getWidth()
+ || mFrame.height() != snapshot.getHeight());
+ mSystemBarBackgroundPainter.setInsets(systemBarInsets);
+ }
+
+ private void drawSnapshot(boolean releaseAfterDraw) {
+ Log.v(TAG, "Drawing snapshot surface sizeMismatch=" + mSizeMismatch);
+ if (mSizeMismatch) {
+ // The dimensions of the buffer and the window don't match, so attaching the buffer
+ // will fail. Better create a child window with the exact dimensions and fill the
+ // parent window with the background color!
+ drawSizeMismatchSnapshot();
+ } else {
+ drawSizeMatchSnapshot();
+ }
+
+ // In case window manager leaks us, make sure we don't retain the snapshot.
+ if (mSnapshot.getHardwareBuffer() != null) {
+ mSnapshot.getHardwareBuffer().close();
+ }
+ if (releaseAfterDraw) {
+ mRootSurface.release();
+ }
+ }
+
+ private void drawSizeMatchSnapshot() {
+ mTransaction.setBuffer(mRootSurface, mSnapshot.getHardwareBuffer())
+ .setColorSpace(mRootSurface, mSnapshot.getColorSpace())
+ .apply();
+ }
+
+ private void drawSizeMismatchSnapshot() {
+ final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
+ final SurfaceSession session = new SurfaceSession();
+
+ // We consider nearly matched dimensions as there can be rounding errors and the user
+ // won't notice very minute differences from scaling one dimension more than the other
+ final boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshot);
+
+ // Keep a reference to it such that it doesn't get destroyed when finalized.
+ SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
+ .setName(mTitle + " - task-snapshot-surface")
+ .setBLASTLayer()
+ .setFormat(buffer.getFormat())
+ .setParent(mRootSurface)
+ .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
+ .build();
+
+ final Rect frame;
+ // We can just show the surface here as it will still be hidden as the parent is
+ // still hidden.
+ mTransaction.show(childSurfaceControl);
+ if (aspectRatioMismatch) {
+ // Clip off ugly navigation bar.
+ final Rect crop = calculateSnapshotCrop();
+ frame = calculateSnapshotFrame(crop);
+ mTransaction.setWindowCrop(childSurfaceControl, crop);
+ mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
+ sTmpSnapshotSize.set(crop);
+ sTmpDstFrame.set(frame);
+ } else {
+ frame = null;
+ sTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
+ sTmpDstFrame.set(mFrame);
+ sTmpDstFrame.offsetTo(0, 0);
+ }
+
+ // Scale the mismatch dimensions to fill the task bounds
+ sSnapshotMatrix.setRectToRect(sTmpSnapshotSize, sTmpDstFrame, Matrix.ScaleToFit.FILL);
+ mTransaction.setMatrix(childSurfaceControl, sSnapshotMatrix, sTmpFloat9);
+ mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
+ mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
+
+ if (aspectRatioMismatch) {
+ GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
+ PixelFormat.RGBA_8888,
+ GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+ | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+ // TODO: Support this on HardwareBuffer
+ final Canvas c = background.lockCanvas();
+ drawBackgroundAndBars(c, frame);
+ background.unlockCanvasAndPost(c);
+ mTransaction.setBuffer(mRootSurface,
+ HardwareBuffer.createFromGraphicBuffer(background));
+ }
+ mTransaction.apply();
+ childSurfaceControl.release();
+ }
+
+ /**
+ * Calculates the snapshot crop in snapshot coordinate space.
+ *
+ * @return crop rect in snapshot coordinate space.
+ */
+ Rect calculateSnapshotCrop() {
+ final Rect rect = new Rect();
+ final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+ rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
+ final Rect insets = mSnapshot.getContentInsets();
+
+ final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
+ final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
+
+ // Let's remove all system decorations except the status bar, but only if the task is at
+ // the very top of the screen.
+ final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
+ rect.inset((int) (insets.left * scaleX),
+ isTop ? 0 : (int) (insets.top * scaleY),
+ (int) (insets.right * scaleX),
+ (int) (insets.bottom * scaleY));
+ return rect;
+ }
+
+ /**
+ * Calculates the snapshot frame in window coordinate space from crop.
+ *
+ * @param crop rect that is in snapshot coordinate space.
+ */
+ Rect calculateSnapshotFrame(Rect crop) {
+ final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
+ final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
+ final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
+
+ // Rescale the frame from snapshot to window coordinate space
+ final Rect frame = new Rect(0, 0,
+ (int) (crop.width() / scaleX + 0.5f),
+ (int) (crop.height() / scaleY + 0.5f)
+ );
+
+ // However, we also need to make space for the navigation bar on the left side.
+ frame.offset(mSystemBarInsets.left, 0);
+ return frame;
+ }
+
+ /**
+ * Draw status bar and navigation bar background.
+ */
+ void drawBackgroundAndBars(Canvas c, Rect frame) {
+ final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
+ final boolean fillHorizontally = c.getWidth() > frame.right;
+ final boolean fillVertically = c.getHeight() > frame.bottom;
+ if (fillHorizontally) {
+ c.drawRect(frame.right, alpha(mSystemBarBackgroundPainter.mStatusBarColor) == 0xFF
+ ? statusBarHeight : 0, c.getWidth(), fillVertically
+ ? frame.bottom : c.getHeight(), sBackgroundPaint);
+ }
+ if (fillVertically) {
+ c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), sBackgroundPaint);
+ }
+ mSystemBarBackgroundPainter.drawDecors(c, frame);
+ }
+
+ /**
+ * Ask system bar background painter to draw status bar background.
+ *
+ */
+ void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+ mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
+ mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
+ }
+
+ /**
+ * Ask system bar background painter to draw navigation bar background.
+ *
+ */
+ void drawNavigationBarBackground(Canvas c) {
+ mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
+ }
+ }
+
+ /**
+ * @return true if the aspect ratio match between a frame and a snapshot buffer.
+ */
+ public static boolean isAspectRatioMatch(Rect frame, TaskSnapshot snapshot) {
+ if (frame.isEmpty()) {
+ return false;
+ }
+ final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+ return Math.abs(
+ ((float) buffer.getWidth() / buffer.getHeight())
+ - ((float) frame.width() / frame.height())) <= 0.01f;
+ }
+
+ /**
+ * Help method to draw the snapshot on a surface.
+ */
+ public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp,
+ SurfaceControl rootSurface, TaskSnapshot snapshot,
+ Rect configBounds, Rect windowBounds, InsetsState topWindowInsetsState,
+ boolean releaseAfterDraw) {
+ if (windowBounds.isEmpty()) {
+ Log.e(TAG, "Unable to draw snapshot on an empty windowBounds");
+ return;
+ }
+ final SnapshotSurface drawSurface = new SnapshotSurface(
+ rootSurface, snapshot, lp.getTitle(), configBounds);
+
+ final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
+ final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+ final ActivityManager.TaskDescription taskDescription;
+ if (runningTaskInfo.taskDescription != null) {
+ taskDescription = runningTaskInfo.taskDescription;
+ } else {
+ taskDescription = new ActivityManager.TaskDescription();
+ taskDescription.setBackgroundColor(WHITE);
+ }
+ drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
+ attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
+ final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
+ drawSurface.setFrames(windowBounds, systemBarInsets);
+ drawSurface.drawSnapshot(releaseAfterDraw);
+ }
+
+ /**
+ * Help method to create a layout parameters for a window.
+ */
+ public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info,
+ CharSequence title, @WindowManager.LayoutParams.WindowType int windowType,
+ int pixelFormat, IBinder token) {
+ final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
+ final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
+ final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+ if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
+ Log.w(TAG, "unable to create taskSnapshot surface ");
+ return null;
+ }
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+
+ final int appearance = attrs.insetsFlags.appearance;
+ final int windowFlags = attrs.flags;
+ final int windowPrivateFlags = attrs.privateFlags;
+
+ layoutParams.packageName = mainWindowParams.packageName;
+ layoutParams.windowAnimations = mainWindowParams.windowAnimations;
+ layoutParams.dimAmount = mainWindowParams.dimAmount;
+ layoutParams.type = windowType;
+ layoutParams.format = pixelFormat;
+ layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
+ | FLAG_NOT_FOCUSABLE
+ | FLAG_NOT_TOUCHABLE;
+ // Setting as trusted overlay to let touches pass through. This is safe because this
+ // window is controlled by the system.
+ layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
+ | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
+ layoutParams.token = token;
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.insetsFlags.appearance = appearance;
+ layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
+ layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
+ layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
+ layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
+ layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
+
+ layoutParams.setTitle(title);
+ return layoutParams;
+ }
+
+ static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+ return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
+ false /* ignoreVisibility */).toRect();
+ }
+
+ /**
+ * Helper class to draw the background of the system bars in regions the task snapshot isn't
+ * filling the window.
+ */
+ public static class SystemBarBackgroundPainter {
+ private final Paint mStatusBarPaint = new Paint();
+ private final Paint mNavigationBarPaint = new Paint();
+ private final int mStatusBarColor;
+ private final int mNavigationBarColor;
+ private final int mWindowFlags;
+ private final int mWindowPrivateFlags;
+ private final float mScale;
+ private final @WindowInsets.Type.InsetsType int mRequestedVisibleTypes;
+ private final Rect mSystemBarInsets = new Rect();
+
+ public SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
+ ActivityManager.TaskDescription taskDescription, float scale,
+ @WindowInsets.Type.InsetsType int requestedVisibleTypes) {
+ mWindowFlags = windowFlags;
+ mWindowPrivateFlags = windowPrivateFlags;
+ mScale = scale;
+ final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
+ final int semiTransparent = context.getColor(
+ R.color.system_bar_background_semi_transparent);
+ mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
+ semiTransparent, taskDescription.getStatusBarColor(), appearance,
+ APPEARANCE_LIGHT_STATUS_BARS,
+ taskDescription.getEnsureStatusBarContrastWhenTransparent());
+ mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
+ FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
+ taskDescription.getNavigationBarColor(), appearance,
+ APPEARANCE_LIGHT_NAVIGATION_BARS,
+ taskDescription.getEnsureNavigationBarContrastWhenTransparent()
+ && context.getResources().getBoolean(
+ R.bool.config_navBarNeedsScrim));
+ mStatusBarPaint.setColor(mStatusBarColor);
+ mNavigationBarPaint.setColor(mNavigationBarColor);
+ mRequestedVisibleTypes = requestedVisibleTypes;
+ }
+
+ /**
+ * Set system bar insets.
+ */
+ public void setInsets(Rect systemBarInsets) {
+ mSystemBarInsets.set(systemBarInsets);
+ }
+
+ int getStatusBarColorViewHeight() {
+ final boolean forceBarBackground =
+ (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
+ if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mRequestedVisibleTypes, mStatusBarColor, mWindowFlags,
+ forceBarBackground)) {
+ return (int) (mSystemBarInsets.top * mScale);
+ } else {
+ return 0;
+ }
+ }
+
+ private boolean isNavigationBarColorViewVisible() {
+ final boolean forceBarBackground =
+ (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
+ return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
+ mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags,
+ forceBarBackground);
+ }
+
+ /**
+ * Draw bar colors to a canvas.
+ */
+ public void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
+ drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
+ drawNavigationBarBackground(c);
+ }
+
+ void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
+ int statusBarHeight) {
+ if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
+ && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
+ final int rightInset = (int) (mSystemBarInsets.right * mScale);
+ final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
+ c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight,
+ mStatusBarPaint);
+ }
+ }
+
+ void drawNavigationBarBackground(Canvas c) {
+ final Rect navigationBarRect = new Rect();
+ getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
+ mScale);
+ final boolean visible = isNavigationBarColorViewVisible();
+ if (visible && Color.alpha(mNavigationBarColor) != 0
+ && !navigationBarRect.isEmpty()) {
+ c.drawRect(navigationBarRect, mNavigationBarPaint);
+ }
+ }
+ }
+}
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 573db0d..384dacf 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -61,6 +61,12 @@
*/
public boolean deferRemoveForIme;
+ /**
+ * The rounded corner radius
+ * @hide
+ */
+ public float roundedCornerRadius;
+
public StartingWindowRemovalInfo() {
}
@@ -80,6 +86,7 @@
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
deferRemoveForIme = source.readBoolean();
+ roundedCornerRadius = source.readFloat();
}
@Override
@@ -89,6 +96,7 @@
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
dest.writeBoolean(deferRemoveForIme);
+ dest.writeFloat(roundedCornerRadius);
}
@Override
@@ -96,6 +104,7 @@
return "StartingWindowRemovalInfo{taskId=" + taskId
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ + " roundedCornerRadius=" + roundedCornerRadius
+ " deferRemoveForIme=" + deferRemoveForIme + "}";
}
diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
index 22b90b2..b914cb0 100644
--- a/core/java/android/window/WindowContainerToken.java
+++ b/core/java/android/window/WindowContainerToken.java
@@ -48,7 +48,6 @@
}
@Override
- /** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mRealToken.asBinder());
}
@@ -68,7 +67,6 @@
};
@Override
- /** @hide */
public int describeContents() {
return 0;
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 1063532..5793674 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -827,6 +827,26 @@
}
/**
+ * Sets/removes the reparent leaf task flag for this {@code windowContainer}.
+ * When this is set, the server side will try to reparent the leaf task to task display area
+ * if there is an existing activity in history during the activity launch. This operation only
+ * support on the organized root task.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setReparentLeafTaskIfRelaunch(
+ @NonNull WindowContainerToken windowContainer, boolean reparentLeafTaskIfRelaunch) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH)
+ .setContainer(windowContainer.asBinder())
+ .setReparentLeafTaskIfRelaunch(reparentLeafTaskIfRelaunch)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Merges another WCT into this one.
* @param transfer When true, this will transfer everything from other potentially leaving
* other in an unusable state. When false, other is left alone, but
@@ -905,7 +925,6 @@
}
@Override
- /** @hide */
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeMap(mChanges);
dest.writeTypedList(mHierarchyOps);
@@ -914,7 +933,6 @@
}
@Override
- /** @hide */
public int describeContents() {
return 0;
}
@@ -1242,6 +1260,7 @@
public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
public static final int HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS = 23;
+ public static final int HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH = 24;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1294,6 +1313,8 @@
private boolean mAlwaysOnTop;
+ private boolean mReparentLeafTaskIfRelaunch;
+
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -1406,6 +1427,7 @@
mPendingIntent = copy.mPendingIntent;
mShortcutInfo = copy.mShortcutInfo;
mAlwaysOnTop = copy.mAlwaysOnTop;
+ mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch;
}
protected HierarchyOp(Parcel in) {
@@ -1428,6 +1450,7 @@
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
mShortcutInfo = in.readTypedObject(ShortcutInfo.CREATOR);
mAlwaysOnTop = in.readBoolean();
+ mReparentLeafTaskIfRelaunch = in.readBoolean();
}
public int getType() {
@@ -1502,6 +1525,10 @@
return mAlwaysOnTop;
}
+ public boolean isReparentLeafTaskIfRelaunch() {
+ return mReparentLeafTaskIfRelaunch;
+ }
+
@Nullable
public TaskFragmentCreationParams getTaskFragmentCreationOptions() {
return mTaskFragmentCreationOptions;
@@ -1582,6 +1609,9 @@
+ mReparent + "}";
case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS:
return "{ClearAdjacentRoot: container=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
+ return "{setReparentLeafTaskIfRelaunch: container= " + mContainer
+ + " reparentLeafTaskIfRelaunch= " + mReparentLeafTaskIfRelaunch + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
@@ -1612,6 +1642,7 @@
dest.writeTypedObject(mPendingIntent, flags);
dest.writeTypedObject(mShortcutInfo, flags);
dest.writeBoolean(mAlwaysOnTop);
+ dest.writeBoolean(mReparentLeafTaskIfRelaunch);
}
@Override
@@ -1672,6 +1703,8 @@
private boolean mAlwaysOnTop;
+ private boolean mReparentLeafTaskIfRelaunch;
+
Builder(int type) {
mType = type;
}
@@ -1742,6 +1775,11 @@
return this;
}
+ Builder setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
+ mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
+ return this;
+ }
+
Builder setShortcutInfo(@Nullable ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
return this;
@@ -1767,6 +1805,7 @@
hierarchyOp.mAlwaysOnTop = mAlwaysOnTop;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
hierarchyOp.mShortcutInfo = mShortcutInfo;
+ hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch;
return hierarchyOp;
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index fda39c1..dd9483a 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -229,19 +229,21 @@
}
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
mProgressAnimator.onBackStarted(backEvent, event ->
callback.onBackProgressed(event));
- callback.onBackStarted(backEvent);
+ callback.onBackStarted(new BackEvent(
+ backEvent.getTouchX(), backEvent.getTouchY(),
+ backEvent.getProgress(), backEvent.getSwipeEdge()));
}
});
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backEvent) {
Handler.getMain().post(() -> {
final OnBackAnimationCallback callback = getBackAnimationCallback();
if (callback != null) {
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 614f962..75f0bf5 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -97,6 +97,7 @@
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -415,7 +416,7 @@
@VisibleForTesting
public InteractionJankMonitor(@NonNull HandlerThread worker) {
// Check permission early.
- DeviceConfig.enforceReadPermission(
+ Settings.Config.enforceReadPermission(
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
mRunningTrackers = new SparseArray<>();
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index a182920..2748ded 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -24,6 +24,7 @@
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__START_FOREGROUND_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
@@ -400,6 +401,8 @@
return ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
case TimeoutKind.CONTENT_PROVIDER:
return ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
+ case TimeoutKind.SHORT_FGS_TIMEOUT:
+ return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
default:
return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
}
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index afea69a..a555ae3 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -158,11 +158,11 @@
private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
static {
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
- MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_0, "0");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_1, "1");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_2, "2");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_3, "3");
+ MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_4, "4");
}
private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
@@ -418,7 +418,10 @@
return Double.NaN;
}
- private static String keyToString(int key) {
+ /**
+ * Returns a human readable version of a key.
+ */
+ public static String keyToString(int key) {
StringBuilder sb = new StringBuilder();
final int drainType = key & MODEM_DRAIN_TYPE_MASK;
appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
@@ -427,6 +430,7 @@
if (drainType == MODEM_DRAIN_TYPE_TX) {
final int txLevel = key & MODEM_TX_LEVEL_MASK;
appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
+ sb.append(",");
}
final int ratType = key & MODEM_RAT_TYPE_MASK;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ebfc4f7..db288c0 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -327,4 +327,14 @@
/** Shows rear display educational dialog */
void showRearDisplayDialog(int currentBaseState);
+
+ /** Called when requested to go to fullscreen from the active split app. */
+ void goToFullscreenFromSplit();
+
+ /**
+ * Enters stage split from a current running app.
+ *
+ * @param leftOrTop indicates where the stage split is.
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop);
}
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 019e3bd..a8d8a43 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -80,7 +80,7 @@
VsyncEventData vsyncEventData) override;
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
- nsecs_t vsyncPeriod) override;
+ nsecs_t renderPeriod) override;
void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -168,14 +168,14 @@
}
void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
- int32_t modeId, nsecs_t) {
+ int32_t modeId, nsecs_t renderPeriod) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
if (receiverObj.get()) {
ALOGV("receiver %p ~ Invoking mode changed handler.", this);
env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeChanged,
- timestamp, displayId.value, modeId);
+ timestamp, displayId.value, modeId, renderPeriod);
ALOGV("receiver %p ~ Returned from mode changed handler.", this);
}
@@ -290,7 +290,7 @@
gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
gDisplayEventReceiverClassInfo.dispatchModeChanged =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
- "(JJI)V");
+ "(JJIJ)V");
gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
"dispatchFrameRateOverrides",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5a0a84b..1ed3555 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -109,6 +109,7 @@
jmethodID ctor;
jfieldID supportedDisplayModes;
jfieldID activeDisplayModeId;
+ jfieldID renderFrameRate;
jfieldID supportedColorModes;
jfieldID activeColorMode;
jfieldID hdrCapabilities;
@@ -129,6 +130,7 @@
jfieldID appVsyncOffsetNanos;
jfieldID presentationDeadlineNanos;
jfieldID group;
+ jfieldID supportedHdrTypes;
} gDisplayModeClassInfo;
// Implements SkMallocPixelRef::ReleaseProc, to delete the screenshot on unref.
@@ -1130,6 +1132,16 @@
env->SetLongField(object, gDisplayModeClassInfo.presentationDeadlineNanos,
config.presentationDeadline);
env->SetIntField(object, gDisplayModeClassInfo.group, config.group);
+
+ const auto& types = config.supportedHdrTypes;
+ std::vector<jint> intTypes;
+ for (auto type : types) {
+ intTypes.push_back(static_cast<jint>(type));
+ }
+ auto typesArray = env->NewIntArray(types.size());
+ env->SetIntArrayRegion(typesArray, 0, intTypes.size(), intTypes.data());
+ env->SetObjectField(object, gDisplayModeClassInfo.supportedHdrTypes, typesArray);
+
return object;
}
@@ -1173,6 +1185,7 @@
env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedDisplayModes, modesArray);
env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
info.activeDisplayModeId);
+ env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
if (colorModesArray == NULL) {
@@ -2163,6 +2176,8 @@
"[Landroid/view/SurfaceControl$DisplayMode;");
gDynamicDisplayInfoClassInfo.activeDisplayModeId =
GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
+ gDynamicDisplayInfoClassInfo.renderFrameRate =
+ GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
gDynamicDisplayInfoClassInfo.supportedColorModes =
GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
gDynamicDisplayInfoClassInfo.activeColorMode =
@@ -2191,6 +2206,8 @@
gDisplayModeClassInfo.presentationDeadlineNanos =
GetFieldIDOrDie(env, modeClazz, "presentationDeadlineNanos", "J");
gDisplayModeClassInfo.group = GetFieldIDOrDie(env, modeClazz, "group", "I");
+ gDisplayModeClassInfo.supportedHdrTypes =
+ GetFieldIDOrDie(env, modeClazz, "supportedHdrTypes", "[I");
jclass frameStatsClazz = FindClassOrDie(env, "android/view/FrameStats");
jfieldID undefined_time_nano_field = GetStaticFieldIDOrDie(env,
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 7393c6f..18d84d5 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -457,6 +457,7 @@
optional bool stop_if_killed = 3;
optional bool call_start = 4;
optional int32 last_start_id = 5;
+ optional int32 start_command_result = 6;
}
optional Start start = 19;
@@ -499,7 +500,21 @@
repeated ConnectionRecordProto connections = 26;
optional bool allow_while_in_use_permission_in_fgs = 27;
- // Next Tag: 28
+
+ message ShortFgsInfo {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int64 start_time = 1;
+ optional int32 start_foreground_count = 2;
+ optional int32 start_id = 3;
+ optional int64 timeout_time = 4;
+ optional int64 proc_state_demote_time = 5;
+ optional int64 anr_time = 6;
+ }
+
+ optional ShortFgsInfo short_fgs_info = 28;
+
+ // Next Tag: 29
}
message ConnectionRecordProto {
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 7e17840..3c4bac8 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -107,7 +107,9 @@
sdk_version: "core_platform",
certificate: "platform",
- srcs: [":remote-color-resources-arsc"],
+ srcs: [
+ ":remote-color-resources-arsc",
+ ],
// Disable dexpreopt and verify_uses_libraries check as the app
// contains no Java code to be dexpreopted.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ad8f7fb..fb451dd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3327,7 +3327,9 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
- android:protectionLevel="normal"/>
+ android:label="@string/permlab_startForegroundServicesFromBackground"
+ android:description="@string/permdesc_startForegroundServicesFromBackground"
+ android:protectionLevel="normal"/>
<!-- Allows a companion app to use data in the background.
<p>Protection level: normal
@@ -3343,6 +3345,8 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+ android:label="@string/permlab_companionProfileWatch"
+ android:description="@string/permdesc_companionProfileWatch"
android:protectionLevel="normal" />
<!-- Allows application to request to be associated with a virtual display capable of streaming
@@ -4914,11 +4918,15 @@
of their associated companion device
-->
<permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+ android:label="@string/permlab_observeCompanionDevicePresence"
+ android:description="@string/permdesc_observeCompanionDevicePresence"
android:protectionLevel="normal" />
<!-- Allows an application to deliver companion messages to system
-->
<permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+ android:label="@string/permlab_deliverCompanionMessages"
+ android:description="@string/permdesc_deliverCompanionMessages"
android:protectionLevel="normal" />
<!-- Allows an application to create new companion device associations.
@@ -6286,6 +6294,15 @@
android:protectionLevel="normal|instant" />
<!-- Allows a regular application to use {@link android.app.Service#startForeground
+ Service.startForeground} with the type "fileManagement".
+ <p>Protection level: normal|instant
+ -->
+ <permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT"
+ android:description="@string/permdesc_foregroundServiceFileManagement"
+ android:label="@string/permlab_foregroundServiceFileManagement"
+ android:protectionLevel="normal|instant" />
+
+ <!-- Allows a regular application to use {@link android.app.Service#startForeground
Service.startForeground} with the type "specialUse".
<p>Protection level: normal|appop|instant
-->
diff --git a/core/res/assets/geoid_height_map/README.md b/core/res/assets/geoid_height_map/README.md
new file mode 100644
index 0000000..849d32e
--- /dev/null
+++ b/core/res/assets/geoid_height_map/README.md
@@ -0,0 +1,2 @@
+These binary protos are generated at runtime from the text protos in ../../geoid_height_map_assets
+and using aprotoc.
\ No newline at end of file
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_height_map/map-params.pb
new file mode 100644
index 0000000..6fd4022
--- /dev/null
+++ b/core/res/assets/geoid_height_map/map-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_height_map/tile-1.pb
new file mode 100644
index 0000000..546dd0d
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_height_map/tile-3.pb
new file mode 100644
index 0000000..eb3fe46
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_height_map/tile-5.pb
new file mode 100644
index 0000000..0243d6d0
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_height_map/tile-7.pb
new file mode 100644
index 0000000..3c2f777
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_height_map/tile-9.pb
new file mode 100644
index 0000000..5e9a480
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_height_map/tile-b.pb
new file mode 100644
index 0000000..c57e873
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-b.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_height_map_assets/README.md
new file mode 100644
index 0000000..800b3e5
--- /dev/null
+++ b/core/res/geoid_height_map_assets/README.md
@@ -0,0 +1,8 @@
+These text protos contain composite JPEG/PNG images representing the EGM2008 Earth Gravitational
+Model[^1] published by the National Geospatial-Intelligence Agency.[^2]
+
+[^1]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
+2008 (EGM2008)." Journal of geophysical research: solid earth 117.B4 (2012).
+
+[^2]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
+URL: https://earth-info.nga.mil.
\ No newline at end of file
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_height_map_assets/map-params.textpb
new file mode 100644
index 0000000..3f504d4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/map-params.textpb
@@ -0,0 +1,6 @@
+map_s2_level: 9
+cache_tile_s2_level: 5
+disk_tile_s2_level: 0
+model_a_meters: 255.0
+model_b_meters: -128.0
+model_rmse_meters: 0.36
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_height_map_assets/tile-1.textpb
new file mode 100644
index 0000000..7fac234
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-1.textpb
@@ -0,0 +1,3 @@
+tile_key: "1"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\244i(\242\224R\216\264\361OZ\221je\251V\246Z\225jE\251V\245Z\225jE\247\216\324\361N\317\024f\214\322\023I\2323I\232L\322f\220\232LdqM\316:\321E\024\354\344sJ)\364\340i\340S\200\315(\030\243\255:\224\032p4\264\340i\300\324\213\315:\232EFz\320\0174\244\323I\246\223M-F\3527R\026\244\rR+S\353\314\r%\024QN\247\212z\324\253R\255J\2652\324\253R\255H*U\251\024\324\213O\006\244\024\264QM4RRRt\244$\342\220{\322a\201\312\363M,I\344P\017\255;\214\360h\245\006\234\r<5(4\365cO\006\226\227\024\264\nPiisNSS)\245&\232M0\232J3Fj65\031j7Rn\244\335F\352\225\033\212\224\036+\314\215!8\242\212)\303\245<S\326\245Z\225jU\251\226\245Z\221jU5\"\324\202\244\006\236)\342\235\2323HNh\2434\204\322SI\244\3154\322\t\n\364\241e\301\346\234]X\360(\371{Ss\315(4\340i\340\323\305<S\251A\365\247\216zQE.i3J\017\255(8\251U\251\305\251\204\323wRg4f\226\243sQ\023M\335I\272\223u\033\252x\232\236\322W\232\346\201\315)\351H\r-8S\305<\n\221jU\251V\246Z\225MJ\265\"\232\221jQO\024\360i\340\323\263I\232;\321\232Bi3IHO\024\302i3M4\334R\216)E-:\214\323\301\251\024\323\301\247\203K\326\201\221\305(4\273\251C\003KE&j@iwSKSKR\006\247f\226\230\325\003\034Te\251\245\250\335K\272\244G\300\241\236\274\374\212J\t\245\024\264\341O\002\244\025 \251TT\252*Q\305H\246\245SR\255<T\200\342\236\r<\032x4\271\2434f\214\322g\336\214\322n\3074\326bi\224\322i3I\232PiE:\235J\0058S\205<\032x4\340psN\316\356\264\224\240f\214R\200{sN\036\364\204zP\033\024\026\246\346\220\265\000\323\324\323\211\246\267J\253!\346\241-M\335I\276\215\364\345\227\212S%p\331\244\315!4\240\342\2349\251\000\251\022\244\002\236\005H\016*E\004\367\251\000>\265\"\232\225MJ\246\244SR\003N\006\236\r?4f\215\324\002)3Fh\315!>\264\204\323M7\223M9\024f\226\224S\201\247\212p\024\340)\324\242\234)\302\235\232\0058\014\323\302\346\244\000\001H\334\216*\022\r\' \323\261\232i\024\332L\322\356\3058=\014\374UYZ\2533sM-M-I\276\220\311\203K\346W\036\335i\271\245\315-<T\212jd\000\324\300\nv\332]\264\365\251\024\323\305H\246\244V\251\025\252P\324\360\324\340\324\354\322\346\214\322n\244\315\031\2434\231\367\2439\242\233\315&\t\240\nZu(\024\341\322\236)\340\323\207ZZu--(\251\024T\200S\361M \322\021\212\215\215\013\232v3\326\243u\305Bi\271\243v)\013UyZ\253\026\246\026\244\335HZ\243g\305\n\371\025\313\223\223E\'J\003T\201\263N\rS\306\370\251\321\352]\364\006\247\n\221i\300\323\301\251\026\244\025 4\360i\340\323\263J\r)4\334\321\232L\321E(\243\036\224}h\3074c\326\224\016)\300R\342\226\234:R\212p\247\212u:\212P*t\\u\251\024sO\307\2454\203M*M\"\305\334\323\304`\016\224\205)\214\271\025VE\301\250I\346\232M4\265V\225\252\271j\214\265&\352\013Tl\324\212Nk\235u\301\244\006\226\232G4\345\034S\226\246SR\251\251T\324\200\323\301\251\001\342\224\032p5 5\"\265H\032\236\032\236\032\234\r(4\354\322\032i4QE8R\343\024\244n?-(\030\034\322\355\357J)\301sF)qK\212p\024\242\235N\002\236\005(\024\365^jQ\351R\016*D\030\0314\270\024\234f\220\221J\016)O\"\242u\300\252\317\212\251/\025\\\2651\232\240\220\346\2533TE\2517PZ\230Z\244OZ\304u\315W*E%\031\346\2274\340jE5*\232\221MJ\032\244\016)CS\203S\203T\212\325 jxjxjxjx4\340i\331\2434Rd\032>\224\242\224\032p\247t\373\264\240f\234\247\035E.=*D\311\030\244\307<\322\342\214S\200\245\3058\np\247\201N\002\244Q\3058\nxS\326\235\272\223u!jM\334\346\202\371\251#\177Z\212y;\n\250\354j\274\247+UKb\242f\250]\262\rUc\223Q\261\246\346\220\2657v[\025aH\305d\221Q\262\203Q\264~\225\021\0304\242\234\005<T\200\323\301\247\203O\rN\rO\rO\006\236\rH\r<\032\220\032x4\360i\300\323\301\245&\222\212)E(\247\212p4\341N\034\365\247\004=\251\300`\323\331r3M\242\234\0058\nu\002\244Z\221Fi\346\225E<\275FZ\224P\302\231\2323NV\3051\2335VV\305V\221\370\252\214\334\324.\325\013>\005BMFM4\232ajj\037\232\246\316*\211\031\246\021M<\036j)S\270\250\207\006\236)\300\323\201\247\003O\rN\rN\rO\rR+T\201\252@i\340\323\301\247\203R\003O\006\234\r;4f\226\212QN\035i\302\234)\302\234*@i\300R\266q\326\220R\322\212Zu(\251\026\245N\224\340(\346\2028\250\363\315.\361H\322SKSKR\027\244.1Tg\227\223U\336O\226\253\226\250\335\270\252\356\325\021zc50\2651\232\225\033\034\323\214\225X7\024\3074\314\322\365\025\t\034\320)E:\235J\r(4\340i\341\251\341\252Uj\2205<\032\2205<\032\220\032x5 4\354\321J)iE8R\216\264\361\326\234)\300\323\205<\032x\346\215\270\240\nZ)A\245\025*\324\240b\244QI\'\025\031~\324\322j6ni\244\323wTm.)\215!=*3)\031\315S\222M\315QH\374T%\351\217\'\025Y\236\241g\244-\221Q\231)\245\350\3631F\372\256\257\212\035\371\247+\002)H\300\372\324\r\301\240\002FiA\247\003N\024\341KE(4\360\325\"\265H\255R+T\200\324\200\324\212j@j@i\324\240\322\322\203J\r8S\2058S\251\300\323\201\247\203OS\212\220`\365\247l\006\243<\032(\240\032\225\rN\247\232\225M6S\232\256i\245\2526j\214\265F\317P\273\032h|S\036@{\325f\340\325y_\232\204\265E$\225]\244\250\313\324m\'\2753}&\374\232\031\251w\361\212\210\032\010\315 8\342\245\r\221H\311\270\344S\261\260u\241B\261\347\212xU\'\031\240\250\354h\002\212(\245\006\234\rH\246\244SR\253T\212\325*\265H\246\244SO\3158\034\321\232Pis\212p4\340i\300\323\301\247\003N\024\340i\340\324\213R\251\300\250\336\243\315\031\247\n\231*PjE4\217U\337\203Q\263b\242g\250\331\252\"\325\0335B\315Q3TM \025\003\2605\003\266*\273\266j\274\204\212\207\314$\323Y\351\273\351CsAl\232pj\211Z\244\315;\002\224qR)\024\222\214\257\035\2521N\025 \247\216:R\032\000\024\231\242\224\032x5\"\232\221MJ\246\244SR\251\251T\324\200\322\212u\024\240\322\203N\006\234)\324\341N\006\236\r<\032\221Z\244\335Lza\3068\244\024\3455:\036*@i\340\342\225\216EWaP\311\305Vf\346\232\315Q3Te\251\214j\007\252\316\rC\236\271\250]\372\324\005\361Lr\030Uv\030\250\230\323\013b\205|\323\225\275i\302Nh\024\240\346\236\265 \024\003@\2239\030\315\"\343w\315\300\247\034\003\3058\032x4\264\204SM&i\300\323\201\251\026\244Z\225jU\251\026\245SR)\247\322\322\321J)E<\032p4\271\247\003N\006\236\r<\032xj]\324\323I\364\247\n\224\034\npj\2205!|To \305V\221\352\2635F\317\305D\315Q3\324fJa|\323\030\344Ug\030\315Tr9\252\356\324\315\370\250\344q\332\243$\032\206F\3051_\013I\347`\322\371\340\016\265p\232\007Z\221H\247\223@\246\276\027\225<\323U\311\353R\016i\302\234\016)\300\321\232Bi(\024\365\251V\245QR-J\242\244Z\221jU\247\322\212\\\321Fi\324\341N\024\np4\271\245\rO\rO\rK\272\234\034R\202\r9y\247\223\232Pi\013\342\232d\250\231\252\027j\254[\232\215\232\230Z\240v#\245@\322Sw\344\3224\230\025RisU\267TNs\322\253\273\021Q\026\246\371\225\034\255\221\232\214\036)\214\334\324l\325\262M74\240\234\324\212r9\251\007\000\324\017\311\241x\251T\323\301\3158R\321\232)3J*E\251EH\265*\324\242\244\024\361R\003N\006\235N\242\2123N\006\236\r.h\315\024\271\245\rN\rN\335I\272\2245N\217\305H\264\342x\250\\\324d\323\031\252\031\033\212\256Z\230MD\307\024\322\303\034\325Iz\234TFP\243\336\253IrI\300\250\231\311\034\232\207q&\220\266\006*\0275\013TD\323$o\227\024\302p*&jh\311<V\306\354\323\t\251\243\373\274\323\324\323\363L#\212h\340\323\301\251\026\236)h\242\212QR\n\220T\213R\251\251EH*@i\302\234)\331\245\006\235E\024\242\234\r-\024f\2274f\2245.\3523OSS\257\"\236\246\225\215B\315L&\242f\250\035\252\026j\210\2654\265B\315P\263UIO\245W\315F\315@\340d\324,\331\311\250\213Td\323\r@\347\006\241g\315\0107\034S\300\330s\236\225\177\'4\365\251T\366\247\322\364\024v\244\305(\251\026\236)sFiiE8u\247\n\220T\212*U\251V\244\025 \247\001N\024\264\341J)\331\030\351IJ)h\242\2123K\2323@9\245\247\255N\215\305H\0174\3622*\026\025\021\025\013T/U\331\252&5\0335D\315P;TM\316j\244\200\255F\0334\331\037\013P\253dT.\374\361Q\226\244\335QHj#\3159\030(>\265\033=l\200\r4\360i\350y\251A\241\216\005,|\2574\273M \353O\006\244\007\024f\200iE:\234)\342\244Z\225jU\025*\212\221E<S\205(\247S\201\315(\245\243\351E\024Q\2323Fh\245\006\224\034S\251EJ\246\246\034\214\323\201\241\205B\343\025]\352\007<UF=j&j\214\265D\355U\334\323\013T/\363US\36256^V\241\034-B\375j3Gj\216R\002\324!\276Z\214\2754\265n\253\323\261\232p\030\251\001\247\021\305:1\201O\307\345L<\032)\331\245\006\224S\251E<T\213R\250\251TT\252*@*@*AKE(\247\np4\354\203\332\222\214\372\322\023I\2323Fh\315\024\271\247\003N\024\361O\034T\250\335\251\343\031\247\023U\345`*\243\275Ww\252\356j\0064\306\346\242cPHs\322\241-\353M&\253\3102i$\000\'\275@zsQ=ELv\300\252\222K\232a~*2\324\205\253l5L\215S)\315H\026\236\005=W\025 \034SY8\250\250\247\nu:\234)\342\244QS(\251\224T\252*@*E\024\360)\333h\305&)\302\224\nZ)\246\212L\014u\244\242\214\321\232Pi\300\323\203S\203S\301\251\024\323\303\363C>\005S\226L\232\256\355P;T,\325ZG\346\243\3631Q\273\347\2450\236*\273\2674\200\346\220\214\232c&[\223\300\250g\340qU\230\361P\261\305C/CU\030b\243&\233\232ij\334\006\244SV#j\264\234\212x\342\244Z\220\nR\274Ug\034\361M\245\025 \024\243\255<S\300\251\026\246AS\255L\242\244\002\236\005H\242\237\212LQ\266\227m\030\243\024b\223m\030\246\220;SN1M\242\214\321\232]\324\340\324\340\324\360\365\"\267\024\026\346\221\334m\252r?5\0135@\355P\263\324\016rj\007\342\242\3630y\241\233\212\205\201\355LRKb\207s\273\002\230M5\210\306\rVu\306qP7Z\205\370\252\322T\004\323\t\246\223[\240\324\212j\304f\255!\251\001\251Tb\246ZRj\263\016i1@\024\360)\300S\200\251\000\251\024T\312*e\0252\324\242\244\025\"\212~(\305&\3321K\212LQ\2121AZiZa\\\347\024\302)\244RQF}\350\335N\rN\004\324\212\330\034\323\032L\232Fo\226\252\273TL\334Uy\036\253\264\225\023I\3151\233\212\254\347\232M\365 \341y\250\207\031\367\246\232k\032\201\330\223M \221PH*\263\236*\263\234\325v<\323\t\246\223[\252je5<ue\017J\235je\247\203J\307\212\212\214R\201J\0058\nx\025\"\255H\242\245QS-L\242\245\002\244\002\236\253O\002\235\2121HE!\036\224b\226\214Q\212\n\324l\246\242a\212a\024\332C\326\233\2323N\006\246Zk\266*\271~i\314\374T\016\325\004\217U]\352\274\215U\313\234\322o4\3269\246/Z\224\266\0056\243s\212\215\232\243=i\340qPH9\252N9\301\252\257P7Z\210\232a5\274\225:\212\236:\262\274b\254!\315J)CzP\\\343\030\244\034\322\201OQN\333N\002\236\005<\n\221EJ\242\245QS(\251\224S\300\251\000\247\201N\305\030\244+M\305\030\244\245\006\226\202)\244TN9\310\250\230S\010\246\232i\024\224\245\200\245\022\340To&\352\217<\3223\361U\336LUg\222\240y*\006z\205\233&\233\272\215\331\247(\346\234\313\315\025\024\234\232\204\365\245\305/j\205\272\223T\346\0309\252\255\311\252\362pj\003Q\265t\010*\302\234T\250j\312\363V\022\236\030\032\025\261\320\323\363\221\3159O\030\247v\247(\247\201J\005H\005<\n\221EH\242\245Z\231jU\251@\247\201R\001K\212)\r%&(\305\030\243\024\021HEF\313Q\262\324ei\204S\010\246\261\305G\313R1\300\342\233\232kTL\330\252\262\2775Y\332\240f\250\331\261Q\026\346\233\236i\353SD2j}\234\234\324.1P=DFi@8\305(\004\214\nc\256\336\265NnsU\010\353U$\250MF\325\321\"\032\231S5*\200*\302\234\nw\231\330S\303\032r\232\225NjAO\024\361O\024\360*@)\341i\340T\212*E\025*\212\231jU\025 \024\3608\245\"\223\024Q\212m\024QK\232Ji\024\302\276\225\031J\214\2550\255DS&\221\206\321Q\221\232\002b\230\342\252\313T\344\252\256j\022\365\013\276M34\n\231*\314?xU\2228&\253\311\336\25352\227\034qD|\212\216j\243%@zU\031\206\r@M1\253\250\351J[\002\205j\231Z\245S\353S)\030\251\027\232\221V\244U\251\025\rJ\261\232\221b&\236#5 B)\352)\340T\200\n\221EJ\253R*\324\200\021\326\236\265 \024QHE%!\024\224QKIE4\212i\024\302\275\351\205i\206<sP\310\274\323U;\320\303\002\240\220\3259\0175NS\212\246\3475]\215DM%*\324\351V\242\030\"\255\0221Ue\252\315M\357OQH\027nj\031\252\224\225]\370\252\322\256j\243\014\032\214\327LOzhl\232x52\232\225MJ\2652\232\235MJ\246\246Z\225EL\2652\212xZxZ\\{P\024}*@\247\352)\340\343\255L\2305(\024\270\245\315\031\245\242\220\212Ji\030\242\212L\321\2323IHi1\232k\014Tl{T/M\350)\215Ue5Q\352\224\334\232\252\374T\014)\204S)\351S\'QV\242\346\245|\214\021P?5\023-DF\rH\2074\3622*\244\243\004\212\251(\346\252\311P\236j\244\313\203U\315tE\363J\265*\212\231jU\034\324\312*eZ\231T\324\310\246\247U5:!\251UjP)\342\236>\224\341N\306h\000\216\224\340\304u\251\025\324\366\305J\030v4\241\251w\212p#\265-&)(\244\'4\224\021\232m\024\231\245\315&x\244\007\0249/\315DEDW&\220\214TRUIj\243\203T\344\035j\263\212\205\2051\2050\255*\212\235\005X\214\342\236_\327\221Q1\007\245\'Z\214\216x\247\001\265M&x\252\322\034\346\251\310j\273\212\200\214T2.\341U$\\V\332\324\253S(\251\224T\313S\245N\265:T\350*t\025:\212x\031\247\201N\002\236)qJ)\334\322\322\201N\024\365\315H\006i\333\005.\334t\244\311\024\231\245\315\024\322)\264QHi3I\232:\322\206\300#\035j3M\353LqU\336\253H3U\334dUI\022\252\310\265\\\212\217\0314\2453@J\231F\005<u\247\3435\013\251C\354h\246\363\221J[\203M\317\006\253Jj\253\014\324.*\"\264\306Z\253*f\264\326\245Z\231jU5:\324\351V\022\247AS\240\253\010*e\247\212x\247\016\224\372p\024\340)\300R\205\247\205\245\013O\013O\002\235\212Z\nR\025\244\331\355M \212NM!\024\332)\247\336\232i@&\244T\241\222\233\345\226\031\364\2462\355\250\330T\016\271\250\031*\273\307U\244N*\244\211U\236:\214G\315?e\033iqKO\007\212d\274\212\214PG\024\303L\317\006\240~j&\025\003\212\205\2054\212\201\305\\SR\251\251\222\246J\235*t\253\tV\022\247J\260\225*\324\213N\024\360)\340S\300\247\201N\305(\024\340)\340S\261O\002\234\026\245H\367\032\227\311\002\232\321q\3050\307\216\325\033% \212\220\307\352*6OJn\332c\n`\0252-HN\0054\363H\006\r1\223\'\232\215\223\025\031AQ2\n\255\"\325YW\345\252\016*\026\031\244)\307\024\230\246\342\232x\240\034\322\366\246\236\234\322\001JG\025\021\025\031\351L+Q:b\253\270\252\357L\353Q\270\251\301\251\226\246SS-N\225a*\302U\204\253\tV\026\244Z\221i\340S\305H*@)\352)\370\243\024\340)\300S\300\247\252\324\251\036MXU\300\247\342\232E4\212\214\2504m\024\233i\245\001\246\030\207j\201\3415\037\226\300\323\324\021\326\220\344\232r\255;\024\240T.\274\324\0141P\260\252\356\265^Q\305Q\2219\250\n\321\212iZ\215\306*#J\253KH\302\231F\354\212k\n\214\n1\326\242q\315V\225j\233\216i\240b\243z\225MJ\246\247J\231*\302T\351VR\254%YJ\231jU\251\001\251\026\236\242\236:T\252*@;\323\326\227\024\340)\301jEBzU\210\340<f\247\010\007\002\234\022\224\2454\212a\024\314R\n\r7\255%\004\003M()\245)\246:M\270\246\232BqL$w\353Q8\025\013\255V\222\2538\315U\221sP4t\2051L+Q8\3153e.\321MaM9\"\230E ZR\271\024\2018\244+\324T2.*\254\235\rTu\346\242#\025\023sOJ\231jt5:T\350*\312\n\260\234U\2048\251\325\205J\257R\253T\200\324\212jE5*\232\221MJ\265 \366\024\242\236\242\245U\315[\202<\014\232\225\260\243\212\022\246\002\224\364\250\030\363Q\223LcM\3154\232L\323wS\201\342\214\321\232ajN\325\031\036\224\326\250\315F\325\013\232\257%VcQ5Bi\244f\230\313Q\224\246\221L\"\230E0\214\322\021H\005=W=\2516\342\232G4\307\213p\342\251I\0363\232\253 \252\354)\205i\026\245SS\245X@j\302\n\262\203\0252\324\352jU5*\232\225MJ\246\245SR\255J\265\"\324\252}j@\376\224\345\253\021\256j\312 \0258\245a\220)@\305.\354P[\212\211\215Fi\246\232i\246\232M0\365\247\003JM74\003\203\315)!\272P1QH3\322\240\"\230\302\241aP8\250\031j\007J\205\201\024\332\017Ja\031\250\330TdTl9\240-!\024\3209\251T`R\021L+\221M?v\252\3123\232\246\351\315B\321\346\242d\250\026\246A\232\261\032\325\244\025:T\313S-J\246\245SR\251\251\226\245Z\231jU\251TT\252)\364\340*E\025n\016\225d\032z\232\225H&\236j2)\246\2434\001M4\302)\270\342\230E4\212i\310\240\236)\205\216i\300\322\346\214\323\032\242jcT-\315B\3035\023\n\211\226\241d\250\312R\025\246\025\2462\346\243\"\231\262\224\255F\303\024*\323\310\307Zn3F*\'\\\016*\273\256E@\361\324-\035D\351Y\351VPU\210\352\302\324\252jUj\221Z\246SR\251\251\224\324\313S\240\251\324T\312\2652\212x\251\000\247\205\251\000\253p\217\226\246\013O\002\234\016\r.\352\\\323MFi\r\'ji\246PE0\323M0\323\010\244\316)CS\263\3055\215Fj&\250\332\243j\215\206j\"*2)\245)\245)\214\225\023%0\245\0331Me\250\331h\013M#\'\232\\\036\324\214\204\014\323\010\312\234\325r1Q\260\317Z\211\226\240u\315e\240\251\320U\204\310\251\224\324\213R\255L\246\245SS\245N\225:\n\235\005XAS\250\251Ui\341jEZ\220\n\225S5f5\300\346\246\006\2349\245a\3050\032\\\322\023L4\207\245(\2468\364\250\311\246\026\246\226\2439\246\236)\206\232E\002\234\r\006\230\334T/Q\036\264\323Q\232k\014\324eh\tF\332c\257\245B\313L+M+\212a\025\031\034\323H\246\323\227\255\0142)\254>^\225Y\205D\302\241qU\333\203Y\310\2652\216\225*\324\253R-L\265*\324\310*\302\n\260\202\254 \253\010\265a\026\254\"\325\210\342-\322\245\3733c4\2331OU\251Pb\254\257LR\201\315H\242\211\016\005C\272\227u4\232J)E-0\250\250\331*2\224\241i\214)\230\240\212LR\205\240\212\215\252&\025\031\024\302)\204SqM+J\005!\024\3223P\262\323\n\342\232EF\313Q\221L\"\232V\2000i\3148\342\230\303+U\330Tl*\t\005Wu\254\325\034T\253R-L\265*\212\231EL\202\247AV\021j\302-YE\253(\265a\026\254*\325\210\320\366\253pr\n\277J\212E\033\270\024\005\247\201R\3069\251v\363OQQK\311\250H9\245\353IFh\315\000\323\201\243\024\230\315!Za\025\033-7m&(\305=S\"\221\226\242e\250\331j&Z\211\205F\302\233K\214\212\000\241\205FE4\212\215\226\243\"\232ED\313L+M\"\220\214P\t9\305\005p\rVa\315F\303\025\013\014\324N\225\222\005=EJ\265*\212\231EL\202\254\"\325\204Z\263\032\325\224J\262\211V\021jtZ\2361\310\315\\\014\252\240/Z\025\210<Q\214\320F)sO\214\363V\200\340\032x\030\025\013/4\322\242\230V\232E!\244\242\200i\342\236\006h\333\315!L\324n\225\036\312iZLsR\240\244aP\260\250\210\250\310\250\312S\031*\"\224\230\305\000\320Ni\244SJ\323\010\250\330S\010\250\310\2462\323\010\244\333\232ENi_\345\004Ub*\026\246c\232\032<\212\300\006\244Q\232\225EL\202\247AS\240\253(*\302\n\263\032\325\250\326\254\242\324\352*U\0252T\242\244\024\374\322\023\232J\2325\357V\024\361O\3154\256i\245i\204S\010\246R\021\2121\305 \034\324\2128\247S\363\221F)\031A\250\331*2\224\302\264\345\024\244TL*\026\024\322(\331\232C\021#\245E$[G5Y\206)\270\315\030\244#4b\243aQ\232a\024\322)\2148\246\025\246\221GN\225\013\222MD\302\243+M\013\3159\206\005sj*U\0252\212\231EL\202\247AVPU\230\305Z\214U\250\326\254\242\324\312\264\361R\251\247\203O\006\235\272\2234\344<\325\264\344\014T\341x\245\333N\333Me\250\210\2460\250\332\243&\223u\000\346\236\r<\032}8R\342\223m#%DR\200\230\246\260\250\331j&\024\300\265,i\223S\004\030\306*\033\230\376Z\314t\346\243\306(\245\333I\214Tl*6Zf)\244S\010\246\221M)\221Q\343\002\243aQ\260\250\312\321\216i\262g\025\316\250\251Uje\0252\212\231V\247AVPU\230\305[\214U\250\305YQR\n3O\006\236\r<S\351qOU\253p\203\212\264\2434\340\224\3420*6\025\021\025\023\n\214\212c%0\255\030\305(4\365\251\001\247\346\234\r8\n\220 \"\243h\361L)Q2\324n1P\232@*P=)\335\2529\016\340A\2522\2475]\3053\024\365\031\244e\250\210\250\310\246\221I\267\"\230\313L\305\0140*\006\246\021Q\260\246\204\'\240\245\331\216\265\004\247\002\260UjUZ\225V\246U\251\325jdZ\262\213VcZ\265\030\253IS)\247R\212z\212\221EH\005<\n\221V\246T\251\223\345\251C`\325\205pE\014)\245i\214\265\013-D\302\233\212B\264\322\264\3221FiC\322\371\225*6je5 jBsHF*\'\"\240a\232\211\226\243\350jT4\346\2467J\255(\252\222\n\212\244Z\030TL\265\031\024\303I\315!\024\322\264\306\034T\004S\010\246\225\245\215y\311\244\223\212\245)\306k\031V\246U\251UjeZ\231\026\247E\253\010\265e\026\254\245N\265*\323\300\247\205\247\201R-H\242\244QS\242\324\341qMn\r9[5*f\247\316E(\244a\221Q2f\241d\3057m\033i\245j\027Z\214\323y\240\023R+\021R\254\206\245\022R\031h2f\230Ni\206\230\302\230V\234\274S\310\342\242j\201\352\273\214\324%piV\202i\206\230\302\243\"\223\024b\232\303\212\215\272T$R\005\243\313\356hn\007\025VJ\25175\230\253R\252\324\312\2652-N\213S\"\324\350*\302\n\235EL\202\247QR\205\251\002\323\302S\202\323\300\251TT\351\305L9\244d&\205\201\210\310\025*#\016\2650\024\243\203A\2445\023.i6Q\345\323\031*\026CP\262Rm\246\221\3158-/AFx\244\0074\271\245\245\353\326\202\264\322\264\233i\370\342\241a\315B\300TEj\007^j3M&\233\232\t\246\342\232E&)\2568\250H\246\021@\024\343\322\243qU\234UIEg\252\324\252\2652\255N\253S*\324\352\2252%N\253S\242\324\312\265:-L\253R\252\323\302\322\343\332\200*U\025*\212\261\027$U\305\010\247\347\0314\327\230.Dc\203O\211\303\2140\0242m84\321\212\221c\315+[\236\324\337 \322\371\036\324\206,Tm\035@\311P\264u\013Fi\233M\024\322i\t\244\315(jx4\341J)i\244\ni<TOQ0\250\332\243e\252\3561Q\323H\240f\214\342\221\2104\001L\177AQc4\322\224m\305\014*&\025\003\214\325i\022\263\324f\246U\251\225*tJ\235\022\247T\251\325*eJ\231\022\254\"T\312\225(JxZp\024\355\264\241jEZ\224-H\274T\200\223R\004\006\244H\361Sc\214\036}\352&C\236*xP\236\247\025q\023\324\212\220\306;\n\215\242\250Z:\211\343\250Z:\211\243\250\031=\252&\2175\023\307\212\205\226\230E&)@\247\250\251\002\323\200\3055\251\271\244\250\332\243aQ\221\3155\207\025\004\211\232\204\256)\207\2321M\"\224-+\014\n\204\212n\332wjk\nc\n\211\206j&\025^E\254\364J\235\026\247D\251\321*tZ\235\022\247D\251\321*uJ\235\022\247T\251\002g\245)\214\257ZP\264\270\245\002\245QR*\324\201i\301i\342\245V\305<\267\245\033\260*H\334\032\2345H\256A\342\247\014\030r)\214\202\241h\352&J\205\343\252\354\224\302\225\023\307U\335*\022\264\335\264\340\265\"\2558\322c4\322\270\250\317\024\335\324\204\346\232V\242e\3050\323\030f\242t\364\250J\320E7\024\360\000\246\260\334*=\264\355\235\351\254)\245j&\025\023\212\205\215B\346\251*T\350\265a\022\247T\251\321*\302%N\211V\021*tJ\231R\245\tR \301\342\234\371f\344Rb\223m(Z\225EL\213\232\235c\315N\220\257\361\032kE\203\305FW\024\240\361Mf8\250\304\205\032\255G85:\316\007z\221nTT\242ezL\212i\025\033.j&J\215\243\025\013GU\244J\252\353\212\217\024\341\232\224\003F(4\306\250\332\243\"\201\326\244\n\rG$uY\306*2\336\264\322\300\212\205\210\246f\216\364\356\270\024\366A\267\212lj6\234\323\037\212\214\323I\246\032\205\305V\220Uv8\250\343Z\260\211V\021*\302%N\211V\021*\302GS\242T\312\2252\245J\022\236\251\203\234T\205\001\034\365\246,T\246:M\264\340\2652\014T\352jU\004\364\247b\232S4y&\230\321b\243h\201\246\204\333FqNQ\223\315Y\215\200\351S\357\243}\005\263M&\230\303=\252\026\025\004\213\236\325\t\267\334y\240Z\001A\267\307Ji\217\024\335\224\205*2\264\306J\205\2050\361M/\216E\006\347\214\032\247<\343\265Q{\203\353Q\371\344\3654\276a=\352T9\251vq@\0304\374\346\225z\232c\255B\303\025\031\250\311\250\335\252\274\225U\372\323\343Z\262\213\232\260\211V\021*\312%XD\253\010\2252%N\251R\252T\312\224\361\035/\227JW\035)\273sF\312P\230\247\001\212\220T\310\330\251G\315\322\230\310A\245\010\330\344\322\265\273c \346\243\020\261\353R\01029\024\306\266\3054B\001\346\244X\300\350jQ\037\024\306LS1\351F\342(\363=\251\013\2554\225\365\244\033OJB\0054\201Q\262f\231\262\232\313Q\354\315C7\313\300\252\304\346\243~*\244\262b\251\311>*\263;9\342\230\310\330\252\316\305\r\t6MZ\216J\271\034\231\024\245\250\006\244\003\220hqP0\250\230T\rQ1\250\034\325g\025b1V\343Z\262\211VQ*\312%XD\253\010\225:%L\251S*T\213\036j@\224\375\224\236^iD4\030\351\276]\'\227J#5\"\241\025*pjl\202*\t\t\3155e`q\232\23599\315XQK\260\032cB\rG\345c\245(CC\naQ\351Q\262S\n\232aZaZiZL\021J3\336\227\024\230\246\262g\2655W\373\302\251\\\217\234\342\252\234\324o\310\254\371\301\346\251\030\231\317\025n\336\323\035EZ6c\035+.\356\307\004\220+5\241dj\221\033\025f)*\312\266i\300\346\254 \312\212V\217#5\013&*)\026\252H*\273T\rP\275[\214U\270\305[\215j\324kV\221*\302%XD\251\321*eJ\231R\244\tR\004\247\204\247\010\363N1\2208\024\303\037\255\001\005#(\355H\006(\357J)\313\326\207L\324b0O&\254\306\212\243\203\223R\201N4\337\251\240\342\231\232Ni\254*\")3\232FL\364\250\331qL\"\233\212\220 \3051\227\024\231\2434\202\241\232\000\340\234sY\322&\302EU\220\342\241\333\270T\220\300\255\332\256\307\000\003\201S\030A\035*\254\366\201\201\342\261/l\366\202@\252?g;rE5\020\206\255(-L\213\220y\241\340h\31755\271\347\006\247t#\351P2\324\022-T\221j\254\202\253?Z\201\315_\214U\270\326\255\306*\344B\255F=*\324kV\021j\302%N\251R\252T\201)\352\231\251U)\341y\241\200\355Le\246\021\212n)6f\202\270\246\322n\"\233\274\232o9\251\341<\363V\326\224\212L\n\215\200=)\002\232\017\024\322\302\230j2)\271\"\232Nz\323)\010\346\246P1Mq\223Q\342\223m.\312\n\341O\025\233p\233\311\300\252o\006:\324\"\"3V \214\203W\221x\247\221Q\262\346\252Ml$\353U\215\222\200F*\224\226![\345\025b\010\031\007\024\351>a\206\034\325`\233_\"\254\261$sP=@\342\253H\265NQU$\030\315U\220\326\254kV\343\025j1V\343\025j1V\243\253Q\212\262\202\254\"\346\246U\251U*UJ\220 \240\2554\2554\2550\2550\256\r7\034\321\203M\"\230V\223e(\2175,i\203V@\247m\3155\206*<sA\351\311\250\330\366\024\334f\215\206\215\206\230\313P\225\246\221I\212\221\006N)XsM\333N\tK\267\035i\030\006\025VH\324sT\245\000\232j\302\032\245\020\340\323\300\305.(\3050\256j3\0350\302\t\311\024\024\000t\252\362\301\225$U=\234\363O\"\242a\305WqP8\252\262\255P\230U9\005l\306*\324b\255F*\334b\255F*\312\n\264\225e*\302\n\260\202\247QR\005\247\001A\024\204Sv\323v\323YsL)I\262\220\245FiUsR\025\013J\204T\200\212]\300R3\203Q4\212:TM \365\246\207\006\234$\002\227\315\364\244\336M!\031\246\225\024\323\036j2\244R\253m4\343\311\315(\245\310\025\013K\223@\220c\025^c\232\250\312I\253\020\307\305LS\212a\\\016)\204RSM\024\322)\244TdsPK\010\373\302\241)\305@\353P2\324.\265ZE\310\252\023%R\221+^1V\243\025n1V\243\025f1V\220U\204\253\021\212\264\202\254\240\251\324T\240R\342\214R\021I\266\220\2554\214Q\2674\322\224\306\035\251\236^i\215\307J\211\331\2155d\"\236\327\001G\0315\013]\023\320SE\303w\244iKv\241\010\357R\0021F\352PsOZ\220\014\323YiB\346\225\320\001\223U\037\031\371i\013\343\251\246\231\207\255Ff\31579\247\n\n\347\265\036Ni\352\233zS\261H@\250\230\n\215\2522Nx\244\335FsHi\206\232j7@j\264\221\343\245Wt\250\035}j\264\211T\345\216\251\274y\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\004\255IDATx^\355\335\321\226\2338\014\000\320\036\372\377\237\2749{\232m\272\035H\006BlcI\367>2\t\030ld\311$\231\037?xfYo\000\000\000`\210\217\353\261\345\343=\300\264n\353\rl\010\001\000\300\347E\005\000\000\000\031<\312Ck\353\000\000\000\323\363\204\017\020\t\3302&h\303H\342\210\233g\t\000\000\300d\324\263W\370\2738\324\003\000\000\000@0\341\2273\302\237\000\000\000\000\260\021\355\307\342\2075\3277\030\000\000\200\234\326eU\344\352g}.\ru\334\365\305\362\236\031\000\000\000\007(\013y)\362\"\021p\206)\201\277\231\005\240\004\241\237\223\226\237\353-\320U\343h\325xw\034\"\273\004\000\000`\237\352\221.\014,\200\227\254\227\003\000@1\212\000\000\000\n\363\330\020\000\200\272,\016\003p\221\321\0132O\247\274\345K;n\267\321\215\002.\36642\000\000P\213J\020\000\306\2122\367Fi\'=X6\204h\304l\310\313\375\235\314\3414\353\360\013\001`\2079\005\340MY\002\347\222\345D \367\037\000\000@2\236\333G3\2602\367\273\014\274\305x\201\310\334\301\000\014a\302Ii`\225JSz\016\000\200\246$\230\305\031\000\000\025\025X\360Mv\212M\347\353\246;\003\372s\323\206\240\233\000\n\032Tt\014:\014\2632\000\000\000\000\240\252\343\217\036\254\037\000\000@\025\235\263\377\316\273\347s\307+\305b:]\230N\273\2459=\005\000@8\007+p\271nZ;\377;\360\373\277\002\344\'\016\262og2\005 \253I\342\377\301\262~>a\033\036\322m\226\361\n\220B\2429\314\364\000@\017\346\027\222J\224\005\002\000\345\311l\000\302\020\262y\305\032\034\000\000\364%\347&\224\027\013\010}\307\361\355\353Q_\264\001\250\304\267l+\373\325\371\006\000\000\344\243\324\203\000$\342\000\360\006k\230\000\033\277\213\177\0012\000\235\004\225\211\000\000\000\344\341\203\010\331\254z\364Q\276\334\326\3376\340\177\313\372\252Q\315\310\001`I\001\000\270\222\\\204\211\030\216\343\214,y\016\233\262Q\326N\200\234\3046\000\210\310\014\376\233\013\221\307\234KAs\266\n\262\021\313\001\000\230\205\334t\270 \227<H3\001\000\000\346\263}\340\252\304\002\250\314\017\214\027\267\374\\o\001\000\312\220\010\002\000\000q\251h\342+\327\207\345Nx\207\353\001@j&:\200Wr\177b9\367\331\3255\240_\007\034\3423\235s\233\315\3567\033\022\230\276\223G\312\330\301\234g<\000\000$%\321#\261k\213\374k\217~R\310FS\324}\3762\211\001\300\365\244\220\314l|\2768\376\210@\024\342\003\000\000\314\302z\026\000\000\000@\024Vr\000\000\000\000\000\000\000\340n\211\366]\345h\355\005\000>g\376\207\366\334WD\341s\337\245\351\376\016\"\305\177\003\240\270\267\007@\244\321\3153\341\026\252\001\242y{n\005\030\357v}\254\272\276\005\000\000\000\000\000@X>\376\002P\234\'\316\347\334\\:\000h\313\314\n\000\205\230\370\001\000\200\231\004\371\350\214R\252\270\t\276\316\016\000\000\000\300\033,\347\0009\210f$\026\344!!\000\320\310\005\037\274\270\340\220\034!\017\344\356\327\r\372\317z#\005\374\t\315\213E\017(h\221\007@I\2339\177\263!\006\021\254\021\027\222\347\202F\006\336%\004\024g\000\000\000\360\214<\021\000\200\324<\002I*Q\307\216/\312\336:\342[/&\212D7\020\224&DCY\207\277\010\262\010\024I\035\034\000\000u\325\230\001\217\235\345\261W]\250m\276b\216\004&\3264\336\001\334\265M\245\276!\313\232\323\260\001\300\\\222\335\220\311N\347\205\0067\353%\027\252\347\017\0076\270&\000\325\010\235\000\274\3201q\'\262O\006\306\355\243w\037\361\310ld8g=z\250wO}\357K\377\035\353\314c\257b\353\332\256\006h\313l\000@\006r\364}\3679\337\205:)\370\205\013\336|`\000q\002\000\370\236\222\362S\255\027\342?\351\214\263\357=\373>\272h:\244\364mu{\303\311\010\231\220N\001\372\370oJ\330\233\030\200\224\"\244\027O\303S\204\206\267\223\366l\307\235\330\270#\r\326\363\267\007\000\000\000\370c\306\352\353\374\217^.\347\337\032\315\214\035\007\227+\023\001\000\000\330\220\013\326\266\314\264 0OKz\362<\037\000\340b\023\344c\0234\001\362\333/\276j\024\241;\016\\\204\307K\376\005s\343\23463\025f\327\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_height_map_assets/tile-3.textpb
new file mode 100644
index 0000000..486adf4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-3.textpb
@@ -0,0 +1,3 @@
+tile_key: "3"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364/\245.\352pz\225d\247\356\3151\316j65\0314\302i\245\251\244\323sFh\315&h\315\004\323I\246\223M\335HZ\232M4\232ija4\302i\204\324d\324d\323\t\250\311\246\023Q\223Q\263TL\325\0235D\315Q3T,\325\0135D\315Q\263T,\325\0235D\315Q\263Te\251\205\251\244\323I\244\315&h\315\031\242\2274\240\323\251\300\323\201\247\003R\242\223Vb\212\264!\213\025\353E\251\245\250\017O\017R\007\247\026\030\037Zk\032\214\232\215\215FM74\233\2513K\232L\321\2323M&\232Z\230Z\233\272\220\2654\232ijijaj\215\215FZ\230Z\243&\243-L-Q\263TL\325\0235D\315Q3T,\325\0235B\315Q3TL\325\0235D\315Q\263TE\251\205\251\204\323I\244\315&i3Fh\315.isJ\r;4\340i\302\245E\315[\215*\364H\000\253I\355^\231\276\223}\033\351\341\351\341\351\301\363O\334)\215Q1\250\211\244\315&h\315\031\2434f\220\232ijajaji4\335\324\026\246\026\246\026\246\026\246\026\250\331\2522\324\302\325\0335F\315Q\263TL\325\0235D\315P\263TL\325\0135D\315P\263TL\325\0335D\315Q\263Td\323\t\246\223M&\2234\204\322f\214\322\356\2434\240\322\203O\024\361R\242\022j\334q\364\253\221\307\264d\325\204\253q-z\000z7\322\356\245\017N\017OW\251\003\346\235\2735\033TML\315\031\2434f\214\323sHZ\230M0\2654\2650\265!j7S\031\2522\325\031jaj\214\265F\315L/Q\227\250\331\252&z\215\236\242g\250Y\352&j\211\232\241f\250Y\252&j\211\232\243f\250\213S\013S\t\246\223M&\232M7u!jM\324\233\251A4\360\rH\020\323\304u\"\305S\254F\254\305\016:\325\250\323\025co\002\245\215*\344K\212\355\003\346\215\324o\305(\222\234\036\234\036\244Y*P\371\240\267\025\0214\302i7R\346\214\322\023M&\232Z\230Z\230Z\230Z\232Z\223u\000\323\034\324L\325\0335FZ\243/Lf\250\231\2526z\215\236\242g\250\331\252&z\211\236\242g\250Y\252&j\205\232\242f\250\231\2526j\214\2654\2650\2650\2754\2653u\031\247\005&\234#8\351J#5\"\307\355R\244C\275N\261\217J\231b\036\225*\305R\004\002\244QSF9\253\210\231\253\021\307V\022:\350\326Jxz\013R\007\247\007\247o\251\025\352U\222\244\017\221\3150\236)\204\323wR\206\245\335HZ\232Z\230M0\2650\232ajijM\324\233\261HZ\240s\212\211\232\243-Q\263Te\2526j\211\232\243g\250\231\3526z\211\236\242g\250\231\352\026z\211\232\242f\250\331\252&j\214\265F_\024\302\365\031~i\013f\232h\000\232\235\"\365\253\t\037\240\251<\272M\230\355O\013OU\315J\253\212\235G\025(\034RT\252*\304K\315^\215j\312%YE\2550\324\360\324\355\324\205\2517\323\303\324\212\365 zx\222\234\030\021McL-\315\033\250\335HZ\220\2650\2654\2650\2650\2654\2654\265!jaj\215\232\240f\305F\315Q\226\250\331\252&z\215\232\242f\250\331\252&j\215\232\242f\250Y\252&j\211\232\243f\250\231\2526lT,\377\000\205F\\w\246\347=)*EJR\242\204Q\232\262\007\313V\020p)\370\243m(Jz\256\rJ\027=*@\265 \024\273y\251\221j\314C\025v<U\250\326\254*\325\260\330\245\337K\276\221\244\246\357\247\253\324\241\351\341\351\302Jz\311N\337\236\264\306<\323wQ\272\215\324\322\324\322\324\322\324\302\324\322\324\302\324\322\324\322\324\302\365\0335B\315Q3\324l\325\031j\211\232\243f\250\231\2526z\211\236\242g\250\231\252&j\211\232\243-Q\263TL\330\250X\236\246\242f\246S\200\245\035j`@\024\335\324/Z\234\036\225j#\225\251@\247\201O\013N\333O\214`\324\333)\300S\202\232\231\026\254\"\036\325b4j\271\0225[D\251G4\326\342\243/Az@\364\340\365\"\311O\017K\346S\326J\225d\343\024\026\244&\233\272\215\324\205\251\013S\013SKS\013S\013SKS\013Tl\365\033=D\317Q3\324e\3522\324\306j\205\232\242f\250\231\3526z\211\232\242f\250\231\2526j\214\2651\232\241rs\234\324li\206\223\034R\321N\240T\2129\253\n\234f\255B\277(\251\302\323\302\323\202\324\252\225(\216\236\026\244T\251\026*\263\025\271n\325r8\002\216je\n;T\311\364\253Q\256j0})\254j\00684\335\324n\245\335N\rR\007\247n\245\337OY)\341\363\212v\352Bi7Rn\246\226\246\226\246\026\246\226\246\026\246\226\250\331\352&z\211\236\243g\250\331\352&z\214\276)\245\352\'j\205\232\242f\250\231\2526j\215\232\230A=x\250\233m0\342\230Xv\024\302F*&\344\323H\243\030\242\212QR(\251Q7\036*\340N\000\253\010\230\002\246U\251\002\324\212\225*\245J\022\236#\251\243\2135j8=j\342\"\250\342\2022i\311\035Y\215*\324Q\346\250\253qC6*\t\rE\272\200\324\340iA\247\206\247\006\243u(|S\325\352@\371\247o\365\246\356\315!8\246\226\246\026\246\226\246\026\246\026\246\027\250\331\352&z\205\236\243g\250\231\3526z\215\236\233\277\024\326z\205\232\241f\367\250\213{\323I\035\3114\302\303\265F\315Q\026\246\223Q\261\246\3474\206\220\2121I\2121N\002\245E\315[\202*\274\261`\n\225R\244\013\212z\256juJ\220-J\251R\252U\204\001G\275N\274\324\240b\225FMX\2162{U\350\255\275j\354p\001\332\271\250\344\342\234NEA#qP\226\240=<58\032pjvh\335F\352]\330\247\253\323\374\312R\340\323Kb\232Z\232Z\230Z\230Z\230^\242g\250\231\352&z\211\236\2432TL\365\031zc=3}1\244\250Z^j6\222\241g\246\357\365\246\226\246\026\246\026\240\364\250\311\244\315-\006\233E\030\247\250\253Q%^\211qV\224t\251\200\245\013\223\305N\211\212\231S5 \216\244T\253\010\234T\211\036MXX\352d\2075a QV\243\214/AV\343Bj\322%p\361\276j\300<T,x5Y\233\006\2205<=H\032\227u;u\033\251CR\356\245\335K\272\215\324y\224\205\275)\205\251\245\3526z\215\236\241g\250\231\352&z\210\275F\317Q\227\250\331\3522\364\322\331\250\331\261P\227\250\231\371\246\026\246\227\246\027\246\227\244\335Jd\342\233\232)\331\242\214R\221@\031\251\243J\273\nU\245\\T\350*`8\247\242\022j\334QU\205JxJ\221c\251\3250*x\343\346\254\254u:ES\244&\255G\020\035j\302(\035*\312-y\324G\275YS\221Ls\214\325)\033\006\232\032\234\032\236\257R\256[\245\004\225\353F\374\322\206\245\rK\272\227u\033\251\013\323K\323K\323\031\352&z\215\244\250\231\352\026z\211\244\250\331\3522\365\031z\215\236\243/F\372\211\3335\013=F\315\3150\2650\2654\2654\265&\352]\324\341\203E8\032x\245\245\0035 LT\211\326\257CVUsV#J\262\261\324\311\035N\243\0252.j\312G\232\220%H\251V#\216\255\"U\204CV\0251R*\223V#\216\254\252W\230F\370\253\010\364\216sT\346\353\232\213u(jpz\265o2\251\371\205\027\022\2536W\212\204?\255H\036\227w\245\001\350\337AzizB\364\306z\215\244\250\232J\215\244\250ZJ\211\244\250\232J\214\311Q\263\324e\3526\222\230^\200\364\307z\201\236\230Z\230Z\232Z\232Z\2234\231\2405<58\032x\247\216i\336\302\246ARb\205\034\325\270Z\257C\315\\\215j\312\255<\014T\321\256j\334q\364\253J\234S\325*t\216\254$uj8\352\312GR\210\352DJ\260\211V\021+\307\204\236\2254sv\251w\346\240\224\344Um\324n\247\006\245\337H\\\232\262>h2G#\275F\262T\233\3517\373\322\356\244\337H^\230^\243g\250\232J\211\236\242i*&\222\242i*#%F\322TfJa\222\243/M/F\372c\265D\317Q\226\246\226\244\335F\352L\323I\240\032x5 5\"\342\247T\317\265.0jt\034S\261OU\251\343\\U\330[\025~#\320\325\201\315J\213\232\271\014|U\330\243\342\247\tR\244y\253\t\035X\216*\260\221\340\325\224J\224%H\211S\204\251\343Z\360\245\233wCS$\2305i\037\"\232\346\2539\301\246\346\227u;u&\354\032\235g%v\346\231\300\357K\276\215\343\265;x\355I\276\232^\2432Tm%B\322TM%B\322TM%D\322Tm%F\322Tm%Fd\2442SK\320\036\232\357\305D^\233\232ijM\324f\2279\244&\220\032z\232\225ML\206\254\253qJ\0075*\n\231V\245T\253\010\265e\022\255\302z\003WQ3\322\255D\225r$\253h*tL\325\204\216\254\244uj8\352c\037\024\344\030\251\325jdZ\231R\247H\353\347tl\032\260\222f\254\306\346\245c\300\250d\342\242\335\2127S\203PZ\233\272\245^A\244\335I\276\223\314\305\036e4\311Q\264\225\023IP\264\225\013KQ4\225\023IQ\231*&\222\243i*6\222\230d\246\231i<\332x|\323Y\252-\324o\2434\204\321\232\\\321\326\201\326\236*U\251\226\254%J\242\246QS\240\253\n\2654iV\321x\251\222>j\334y\025v\023\232\277\032f\255G\035\\\216.\225e#\305Y\216<\325\225\217\024\375\264\252\225:\245N\211V\022:\235c\257\232\310*y\247\253`\325\330OCS\261\252\362\265@Z\215\324\340\364n\246\227\251c\220r3H\347\322\2432SL\224\236e4\311Q\264\225\013KP\264\225\013KP\264\264\303%F\322TfJ\215\244\250\332J\214\311I\346f\232_\024\364\222\236\355\306j\035\364n\245\017N\316i3\212p4\361N\003\232\221EH\242\246AS\240\251\320T\350\265a\022\254\306\265a\022\255\242\000\265:\307\306sR\242\342\255@\270<\326\244\003\201Z\021%ZD\253\010\225f5\253\n\264\375\224\365\216\254,ub8\252\312GR\004\257\230\244}\346\233\320\212\271\031\332\005M\346qU\344\223&\242-I\276\224=.\372ij\217~\rX\337\362\324\014\364\303%!\222\232d\250\232J\201\344\250^Z\201\245\250\232Oza\226\232d\367\250\332J\215\244\250\332J\214\311M2R\371\231\247#\324\305\362\265\016\356iCR\251\247\251\301\247\236FE*sR\250\346\244QR\252\361OU\315N\213S\242\324\350\265f4\253H\225:%Z\215*\312\002F\000\251\322?Z\260\221{U\250\343\253\360\'J\321\204c\025q\026\254\"\324\352\2652\212\231W&\246\t\322\254G\035YD\251\325i\341y\257\224\267R\253d\365\251\326\340\214\017J\223\317\310\250\231\362i\273\251\013R\207\243}4\2751\236\244Ir\234\324L\365\031\222\232d\246\264\225\013IP\274\225\003\311U\332J\214\311L2SL\264\303%4\311L/Q\226\246\356\247\003\232\221x\247\227\342\242\r\363S\263\203OV\251\001\315J\207<\032z\256\rN\026\244QS*\324\310\225:\307S\"U\250\343\315Z\216:\266\221q\232\231#\253QGV\322!\216\225:GV\022*\263\034uz\010\375\252\342GW#\\\212\265\032T\252\2652GV\021*UNj\332\'\0252-L\027\212r\212\371\'u\001\261N\rO\017\212\013qF\352M\336\264n\244-M/Q\263\346\204~\324\326|TfJa\222\230\322T-%B\362Uw\222\240i*&\226\243ii\276m\'\231M2S|\312\013Te\371\247\254\270\251<\352x|\212f~j\223w\024\34452\032\225\0175eFj\302\n\221V\246E\253q&j\312\307\355R\244Uj8\261\212\267\034Uv8\270\305J\260\373U\230\341\366\253+\037\250\253\021\307VV:\2364\253\221\014U\224>\225n,U\225\"\247J\262\213S\252T\250\234\325\224\025:\212\230\nP\265\361\376\374u\245-\273\221\320Q\346d\323\267r*U`x\244\'\232i4\233\251\013S\031\2522\324\253\300\3156F\342\2533\342\2432\323\032J\201\344\250ZJ\205\344\252\355%D\322TM%0\311I\346\322\371\224\236e(zij\025\351\333\261S\304\324\245\271\251\024\344S\324\342\246CV\"\003<\325\330\370\351S\252\324\312\275*dNj\364)\322\255\"U\244\2078\342\247X\261V\243N\225n5\2531\240\253J\243\002\235\220\rH\216;U\204b{T\350O\245Y\2103\032\273\032b\244\335\264\342\244B\331\310\346\255G&:\365\253qL*\332J*eqS\243\n\260\246\246S\221R(\257\215\213z\232\2267\302\034TA\260\334\324\241\263\315(~})\305\275\r7y?J\013SKTl\324\300\334\323\214\230\353PI(#\212\254\362T-%1\244\315B\362T\r%D\362Ui$\250ZZ\215\244\246\031)<\312Q%\033\351D\224\375\331\024\014\346\236\017\025,MR1\311\342\244\211\275je\301\251\220b\255F*\334~\365m{T\350\275*\334q\216*\324k\212\271\032\325\350\0235d\303\200\r*\340T\310\330\251\321\316zU\224\311\251\304\031\031<\324\251\t\354*\322[\271\253p\3321#5\261ib\253\202ji\342\000|\242\250\264M\273\245^\266Q\2140\253&\327w+Q\230Y)\213#\206\305\\\216V\342\255\307!\305[\212L\342\256\306r*\302-|`i\276a^\224\007,j`\330\024\273\263C\034\n\013`b\233\272\220\267\025\03357uG#\324\016\365]\336\241i*&\222\241y*\026\222\242g\252\362IU\332J\214\311L2Ry\224\242Jv\372pzz\275J\0374\360jD\251~\265,uaz\325\204\031\2531\n\270\203\000U\270\327\"\255\"t\253\221-\\H\363V\342LU\310\260\265)\223#\002\234\210I\346\255\305\026x\002\257Ed\314>\355\\\207On7V\214V\034c\025n\033\000:\212\273\035\242\000\t\351S\013d\316EXED\357R3G\214\001\232A\0227U\251R$\037\303R\205\031\371F\005+\303\221\3275\017\331\207a\315*\246\323\310\247\026\njX\344\346\264\255\344\r\212\320N\225\361a4\323@\342\236\0374\340\3243d\nB\334\234\323Kb\233\273\212a99\246;\342\253\273\324,\365\003\275@\317Q3\324N\374Uf\223\232a|\212\255#\325v\222\2432S\013\323|\312z\311O\022S\203\324\213%J\257R\253T\361\265MR\306j\312u\2531\212\267\020\315[J\273\n\360*\364i\234U\270\322\256D\265eH\025&\342G\025=\274e\316\024d\326\315\256\230\317\202\303\025\255\005\212G\216+A!T\034\212\231B\2020*\30279\002\246POJ\225b=\352e\2074\357\263\234\325\224\266P2y\251\004c\037(\247*T\241)|\2726b\221\220\021\315f]\023\033c\265Ij\306LV\244A\243\301\355Z\2606\345\025\361ni\271\2434\241\251CR\223J\314\n\202z\324d\344\322c\202)\214\330\252\356\365\003\265@\317P;\324\014\365\013=F\317\305U\221\351\202L\212\206V\305Tg\250\214\224\205\351\273\351\301\352Epj@\342\236\257S\253T\310j\314|\325\220*D\0305f>\265r1\305Z\214U\310\3278\253\361/\025\241\n\360*\352\'\002\246^*tN\346\255End<V\355\205\232FA\3075\265\033\0000\005J\2715aA=jdJ\260\211VcJ\260\251S\242T\253\036\343Rm\344\212\220F})\351\027s\326\237\260R\204\240\306)\214\225\233{\016\3420)\326p\354\353Z\341r\200b\255Z\251\003\006\276+\315&h\316h\315(4\273\251r\017\007\2458\000\0055\333\002\252\310\371\252\356\325\003\265@\355U\335\252\006j\205\236\242g\252\322=B$\301\250\345\220\232\246\317\311\250\313\322o\246\027\245\022T\210\365.\342*Dj\265\033U\2045f#V\321\252d\253q\n\271\030\351\212\265\030\253\360\247\002\257\302\274U\370S\201V\301\300\251\341M\307&\256,E\210\013[6V\273PdV\254H\007J\264\213VQj\312-XE\253\010\225j4\251\325*A\307\025n\025\030\247\010\306\356*`\224\316\344\nz\212~\007\343HV\223h\006\240xC\222H\2428\300l\n\323\212\037\224f\247\216,\034\327\304\006\2234\231\245\315.h&\223u(\227\002\242\222M\325\0035@\355P;Ugj\201\332\241f\250\035\252\026j\255#Ugza\223\212\255#|\306\242-H^\232^\227p\251cz\234\020E=MY\211\252\322U\250\252\352\016*t\025n!\322\257B=j\354KW\241Z\320\201j\364`\001R\250\334\303\025\243\004Y\300\025\265kl\240\002\302\264c\003\265ZAVc\025f1VPU\230\326\255F\265j5\315YU\240\307\316j\334\021\222\265<Q\374\307=\251\345qP\221\226\342\244\002\245*\252\276\264\305 \212B\200\362M5\306@\002\237\005\271$\034V\200\033p*@k\341\243L&\2234f\224\032Bi7SKTL\325\0235B\355U\235\252\273\265@\355P1\250Y\252\027j\253#Ugj\210\265A+r*\"\324\302\364\205\351C\324\310j\322\221\266\234\244\346\254\304\325r3V\342\355Wc5j1V\342\034\212\277\020\253\321\014\325\370V\257D0EX\317LU\313u\307&\266,\"\334A5\260\270\030\002\254\305V\322\254\245Z\214U\230\326\255\306*\314b\254\3060EZAO8\034U\373|2b\235\267nsQHK\034\nhV\317J\231P\234qJ\352FEV(\301\370\351V\022\022\335j\302\333\201\326\254$x\245e4\252\276\265\360\333\n\204\365\246\223FisHM&i\244\324Lj\0265\013\232\254\346\253\271\252\356\325\013\265B\306\240\221\252\244\215U\235\252=\325\024\255\305BZ\243&\232Z\205j\261\033\325\205z\231\0335j#V\3428\253\321\034\342\256F*\324uv*\320\206\257\302:U\370G\"\255\216*\335\272n\344\325\330\027{\214t\255\353e\021\240\035\352\342\032\267\035[\216\254\307W\"\253q\212\267\032\325\230\326\254\306\274\325\2001A^sWm\270\305Y\312\263`\323\035\0009\035*\274\227+\031\250N\247\351\201B^\031\\\016\265\251\034a\324\020*\302\305\216\225*\246jU\2175(\204w\250\335\0247\025\360\223\032\205\2523Gj3A4\334\323I\250\232\241cP\271\252\356j\263\232\201\315@\346\241cU\244j\252\355U\2445\036j)\017\025\016x\246\023M&\224\032\225\rYZ\2363V\343\253\221\325\350j\364F\255\307\315\\\213\265_\207\265h\302:U\370\370\305N\2373\001Zp!\332\005hZ\307\206\025\250\215V\3429\253\221U\270\315Z\216\256EWb\355V\3435n>j\302\2561S\016i\315\320T\320\266*u9<\365\245\221\266\216\265\227:\231\033\212\317\2226F\255]&\r\356\t\256\215#\n8\247\242\234\373T\333x\244I6\2674\351&\335\302\324Y\311\346\276\024\"\241j\211\250\006\202qHM!4\302i\214x\250\036\253\271\250\036\240z\256\365]\352\007<UY\rU\220\325w4\300y\346\242\220\365\250\r0\232J*D5j6\342\246F\346\256D\325z#\232\277\r]\205sWc\\U\310\205_\204c\025\241\025]V\033j\335\252\344\346\265`\255\030\260\243\212\267\031\315\\\214\325\310\232\256Fj\334f\256Dj\344f\256EWb\346\255\240\315J\027\024\377\000+&\225>Y\000\253\312\200\214\212I-\367P\266H{U;\273\020\275\005K\246\257\226\370\255\325PE;\201F\340i\n\212B*&\310\351_\016:b\2538\305@\324\212y\247\036\264\323M\3150\232\215\215D\365]\352\026\252\357U\336\253\275@\346\252Jj\244\206\253\261\246u\250\330\324,j2iz\nAOZ\231\032\255E\315^\210U\330x\255\010{V\204#\030\253\2503V\343\030\025r#\216\265r)*\354J\315\327\245_\205\266\340V\255\250\316\rh!\305Z\210\325\310\215]\214\325\250\315\\\215\252\344MW#j\271\023U\330\232\257Ds\212\2621\201Rn\003\245B\315\363\325\250\346!1K\346\2615<R\034\202j[\225\014\231\252\021\270\216Q[1\311\205\006\223y,EH\264\356\364\206\230y\257\210\235A\025RT\252\216\274\3231\203JO4\323M\2465D\306\243sP=B\365]\352\007\025]\352\254\206\252Jj\253\234\325w\246\257z\211\272\324\rL\240\232L\324\212jx\352\324uv#W\242\347\025\241\017j\320\207\265hF8\2531\324\350{\n\275\016\027\031\353Z\020\313\221\201W\355\343.sZ\220\260A\216\365r3\232\271\021\253\221\232\265\033U\270\332\256F\325n6\253q\265\\\215\372U\370[5z\'\253(sO$\366\246\204\346\246E5:%Y\215*r\273\220\212\307\234\025\227\217Z\320\266\237\200\255VP\202\306\247\024\242\202j2k\342f\025\013\256ER\225pj\022\265\023\036\271\246\253v4\036\264\323Q\260\250\232\241aP\270\250\034Uy*\244\206\252\310j\244\206\252\271\305@\335i\005B\335MB\324\312c\036h\024\365\2531\325\310\205\\\217\222*\3645\241\rhC\332\264\"\346\254\216\005K\t\3475ad\313`V\275\234D\201\232\327\215\202.\005O\023sW\242j\273\023qV\321\252tj\265\033\325\350\236\255\306\325r&\253\221\265]\205\361W\241|\325\304l\324\3523R\005\305J\242\254 \251\327\212\232.s\364\254\353\210\201r})\321\240\0035j\023V\001\247)\244\315FO5\361ST&\253\310*\253u\250$\353P\323\263\221\357HzS\032\243aP\260\250XT\022\n\247%T\220\325Y\rT\220\325g\250Z\232;\324-\336\230i\204TX\311\251\222\002\325!\210\255=\005Z\216\256\304:U\370E_\207\265h\300j\364b\245-\212zK\205\253\226\274\260&\266\241\220\200\000\253\220\271\'\232\275\021\253\261\265\\\215\360*\312IV\021\352\334MWaj\271\033U\310\232\256D\371\253qI\316*\374M\214\032\277\t\357V\343~qVP\206\251\002\355\251\026\245\335\201K\024\303\232\245qq\202\331\357L\212\1770\205\025\243\027\002\246\315*\236iI\353Q\232\370\271\226\240aP\270\315T\221H\317\245U\177Z\214\212fpi\364\204TdTL*\027\025VJ\247-S\222\252IU^\253\275B\302\233\332\2414\334Pc$Tay\253\260\201\306jFPA\305F\253V#\025r!W\241\253\320\326\2045v6\300\247;f\233\031%\200\255{a\2001Z\220\267\025v\023W\242j\267\033U\224z\263\033\325\250\332\256\304\365n\'\253\261\275[\211\352\344MV\324\364\305^\267\2238\006\264\340|qVP\374\325a%\305^\211\204\213N+\264\373S\325\014\247\013J\366\376R\234\034\361X\272\211*F*]92\0015\256\235)\333\250\r\3158\2650\265|j\353P\262\324,\265\004\211\220j\204\313\216\225\\\323\030R\253v4\374qL\"\243aU\344\252rU9j\234\225VAU\\Uw\025\t\024\323\322\242\"\205\\\323\312\361L\331\315L\203\024\346n\324%X\214U\250\352\364=\005^\203\255^\216\254\253b\236\032\226&\303\214\326\265\273qZ\020?j\277\023U\310\332\255\243\342\247G\2531\275\\\215\352\334oW#j\273\023qW\"j\275\023t\253\2617J\264\234\034\212\275o!$V\202\265L\206\254E!C\221Z(D\210\rIn\n9\367\251\'\031J\306\324\340$\006\247\331\r\250+@\032\t\244\006\234O\025\021s\234\036\265\362\003-FR\243h\352\007\217\212\316\236>MTe\301\246\025\246\355\3475&8\246\225\342\242qUd\252rUI\005T\220UY\005V\220Uw\025\013-4\255D\313\3159\006\005;nW4\230\246\223\212h952\n\263\030\2531\365\253\261U\350{U\350\315M\232x<RFI\220V\275\273`\n\275\023\362+B\027\253\221\275XG\253(\365j7\253Q\275\\\215\252\344OW\242|\325\270\236\257\302\365v\'\253\261\266j\334M\202+F3\275x5<G<\036\265>p*{{\222\204/j\322I\200 \372\324\3228#\216\365\235\250\260\362\300\250mxQW\003Rn\245\315.\352%Q\267x\355_#\262TE1Q\225\250]*\224\361g5\237\"`\363P\225\246\225\247\017J\030qU\344\252\222UI\005U\220Ui\005U\221j\263\255Wu\250\212S\031j=\2314\360\231\340R\274ei\241)\222\257\034Th\265e\026\254 \251\343\034\325\330\252\3545r:\237\265*\032\222 \004\2315\247\t\343\212\267\033t\253\3617\025n6\253(\325b6\253q5ZF\253q5^\210\346\256D\330\253q\275]\211\352\364Rr+B\'\253\221\232\275n\3705qFy\035j\300\345pjkU\035\372\325\254g\247j\236\',pz\n\316\324\244\314\241i\320\034(\251\367Q\272\200\364\355\324\273\262\214\t\342\276Rd\250\214t\302\225\004\213UeL\326t\361\363U\331)\205=)v\2201Q\222T\340\364\250%\252\222\n\255 \252\262-U\221j\263\255Wu\252\356\265\036\332\215\222\221S\034\324\210\203\322\234c\315F\321\342\242e\317Zb\245N\213S\250\002\246\214d\325\270\305\\\212\255\307V\0074\252>j\220\374\244\032\277n\331\025n3W\342<U\230\332\255+T\361\265[\215\261Vcz\271\023U\370\037\025y_ T\350\374\325\310^\257B\375*\374RV\204\022\014\001V\243\223\232\320\212L\212\264\255V\"85e[\232\231[\0035\215;\371\227\'\332\255\306x\2517PZ\223u8=9[\203_-\236j29\2460\025\021Pz\325yS#\212\243,95]\355\375*/+\035i\254\265\004\251\306j\254\202\252\270\315Wu\252\316\265Y\326\253\272UgZ\201\326\243+Q\224\311\241\223\260\247\252b\234x\250\\\346\243)H\027\025*\n\223mX\211qV\220U\250\352\334uaE?\035\351G\315\305]\267\\\001Wc\253q\232\265\031\251\321\252\324f\254#{\325\230\332\256\304\365v\026\253\250\374T\350\374\325\330Z\257D\365z\'\253\2615]\215\352\344S\205<\236*\364R\206\344\032\262\222b\254$\231\247M8X\2175\231\021\313\226>\265y\033\212v\352M\324n\2405H\255_1\025\250\312\324l\265\023\n\211\327\212\256\311Q\025\333\332\240t\311\250Y*\t\027\212\245\"\365\025U\306\r@\353U\244Z\256\353U\335j\264\213P2Te)\004tyy4\273*6\\\323<\274\363OX\367R\233zO/m=V\246AVPU\224\025f1VR\244\305 \030j\277\017J\264\206\254\304j\334f\246CV\024\342\245G\2531\275\\\211\353B\007\343\236\225ie\315X\215\363W\242z\273\023\325\370_\245^\211\352\344oR\003\271\261\232\323\200\225P\005[V\251\267\0208\252SL\354v\223SC\300\025d5.\3727Q\272\22459Z\276me\250\212\324l*&Z\211\205B\313\326\242d\250Yj\027Z\253(\252R\202\017\025M\371j\215\226\253\272\325wJ\257\"UvL\232\211\322\242\331\223Jc\300\244\tA\216\233\345d\322\230\251V,\032\231\"\315#Z\363Q\030\n\366\247F\234\325\225Z\235\026\254GV\024T\242\223\034\325\2503\212\271\0375f>*\312t\251\220\363V\024\324\250jt`*\334-WRLU\230\3375j\'\301\253\360\276j\364.\001\031\253\310\300`\203Waz\275\033\361Vm\206\351FkMxc\355J\267J\247\031\251\305\300\333\234\325r\333\3375e\016\005I\272\227u.\352]\324\241\251\352k\347\246\207\212\201\243\250\314u\013\2475\013\'\265B\353P\260\250XT\022\n\251 \353U\312d\034\326s\256\030\212iZ\211\322\240d\252\322&N*#\026\005B\351\236(\020\200=\351\217\035\'\227\212<\2726zR\371t,y52E\203S\371{\227\336\230\326\371\355Q\233r\247\212x\216\244T\251\025pjt\025 \004\324\210\207\251\2531qVc\253h8\0252\364\251\022\247S\300\251W&\247\214z\325\244|t\251\343z\271\023zU\3049\372\325\270d\305]\211\352\364RU\350^\257\305%^\265\227ksZ\261\220Ww\255S\272\217k\006\007\203R\303\222\006MXQ\212\2206)\341\251CS\203R\346\234\032\236\246\274\r\333\002\253\261\250\331\261P3TMP\262\346\240u\367\250\035N*\006\004\216\225ZD89\252\344qT%O\336\032n\312\215\226\241t\315Bb\357PH\230\246,<d\3222\034\323\032>:Q\345\361Hb\243\312\366\243\3134\242<\032\220-9x\251@\004PR\233\260f\245H\201\024\361\020\251\025\000\251UG\245J\0234\021\264\200*\314c\246j\332\216)\352jU\251\324\360*t5:5J\246\245F\253\260I\212\273\033\346\255\306E[\214\325\310\236\257C%^\211\352\3442a\205lE\'\356\361Q\334I\362\250\245\205\361V\203\346\2245?u(l\323\203S\203S\303S\201\257\004qP\271\250\035\352\273IQ4\265\023\313\305Wi*&\222\231\2734\307\\\212\252\311\212\253:\014g\275C\214\212\215\226\240n)\215\310\250\032=\306\244\021|\265\031\206\230c\244\362\275\250\362\250\021\212_&\223\312\244)\212P\225\"\255?o\024\322\234\323\324b\236)\300T\213\301\251\327\326\243\335\271\352\304g\221V\225\252E5*\232\225ML\206\246S\306jEl\325\2045b3W\242n\225r6\253\221=\\\214\325\330\217J\271\023\325\244~\365~\332\344\343i54\222n\024\350\332\254+\324\241\251\341\251\301\251\301\251\301\251\301\252Ej\360\211\rU\221\252\244\215U]\352\026z\211\236\242f\246\023\232r\212y\\\212\202H\370\252\222G\236\265\003\304T\373S\032<\212\254\361\020i\2062h\021\340R\355\342\243d\250\331(\tHc\244\362\361I\202)z\323M\"\216j`\005;\024c4\230\346\235\212x\024\341R\216\225]\270\223\332\255\305\332\255)\004{\324\253R\255J\2652T\240\372\324\253S#U\210\315[\215\252\344MW#j\273\023\325\350\232\255\306\325e_\212\232\027\303\n\272\357R#T\352\365*\265J\032\234\032\234\032\234\032\236\032\236\255\315x\\\246\251\311U$5Y\315@\306\242&\232E&)V\246Q\305+G\362\232\241\"\340\325yNF)\241\306\323\270T$\206\351HPb\232R\233\345\346\220\307M0\346\232\321\342\243+\212i\250\230\3233M\335\212i\220\n<\352p\236\244YjU9\247\250\251DD\364\247\213f\245\362\266\3655\033\305\236\2254Gh\346\246C\310\253IR\250\251TT\312)\343\212z\265J\246\254Fj\334g\245[\215\252\344mW\"j\275\023U\330\216j\322\216*x\370 \324\373\363R\306\334U\204j\231Z\244\rO\rO\rN\006\236\r<5xt\225RZ\247%Vz\201\2522(\305.\332\002\363VR?Z\216\346@\243j\325\006\250\035\t\250\231\030\016\224\301\031\317\"\237\266\232\302\232\0074\2458\246\221M`\030Ui\001\006\242\'\025\003\232\205\237\025\033IQ\027&\22015*\006c\300\253Q\333\310q\301\253q\333H\007\3355e-\237\031\332i\342&\035\2158\254\200t5ZF#9\250\222|\036j\310;\271\025*d\n\271\031\340T\340T\242\244\006\236)V\246Z\231*\324f\255\306j\334f\256D\335*\364M\322\256\302\325v6\315\\\214\003\031>\225\t\233i\251b\270\025n93V\025\252e4\340i\340\323\301\247\003R\003^+\"\3259\227\255Q\220u\252\316*\002)\204Q\212Z\232\030\363\311\245w?\303U\232&s\223J-\375E\006\334SL\000\366\250\244\200\016\325Y\223\031\250Xg\2450\214\032p4\326\3053\245G\"\203U\\b\252\310*\273)4%\273Hx\025m4\247n\306\255E\242\271?t\342\265\255tE\030$\001W\377\000\263\242U\343\255=mbP8\247\030\223\267\024\326\265N\242\201j\230\346\250]X\214\235\2039\254\271,\212\234\343\024\350\240}\330\305h-\266V\244HJ\365\253\n\274T\241x\244<P$\002\244\017R\253f\254Fj\312\036*\304mV\342j\271\023U\330[\245]\210\325\310\332\256B\340\002\017z\204\304\314\307\236*E\201\207\275X\2100\351V\243\223\326\255#T\231\247\003O\006\236\r<\032\361\347J\2472U\tV\252:\324\014)\204R\021M5n\022\014|S\322\035\334\323\314@v\246\230\352&\\TG\255\014\241\205U\232.\rS\333\202j\0318\250w\363K\234\322\212cT\016\231\250\014\005\317\002\245\217N,\303ul\331\351\n0H\255d\260\215\000\342\236#U\340\nF8\246\022MF\331\246\362)7\237Z\004\236\364\355\343\275C2#\203QG\030\317J\262\024\001\236\364\207\031\245Z\220t\250\035\300\004US!\315M\034\276\265n6\310\253Q\265ZC\232\235\rY\211\252\344mW\"j\275\023U\330\333\245Z\215\252t\346\254%K\264u\035i\244\367\035EK\024\231\253hr*AN\024\361O\006\274\226E\252\223/\025\237*\3259\026\253\260\346\243\"\232E4\2415,D\250\305_\200eO\024\346\004\236)\276Y\357P\312\270\252\376]!\\SfO\220\326pB[\030\246\334A\204\310\025\232F\032\244QN#\002\230FjH\355Z^\202\264\355\264\262{V\202YG\037Q\315M\200\275)\215%1\237\322\243$\236\264\224\322i\204\323MFN)7R\026\240>:S\274\312PjE5 <Uk\210\333\250\351U9\035i\312\325j\027\307Z\275\035\\\214\234sS+U\230\332\256Fj\344G\245]\211\272U\330\333\212\265\033U\250\332\255F\3258\366\244x\311\345z\324`\0259\253p\311\221V\223\232v1N\006\234\255^W%S\230U\031\207\025FQU\210\246m\315K\0349\353S}\234c\245:;M\307\245\\Kr\203\030\2462\200x\245+\201U\234\014\363\315D\330\003\201P2\346\227fF\rD`U\344\n\255r\000\214\326)L\261\251\025qC\220)\221\251v\300\025\277\247\331\034\002\303\025\247\263`\300\250Y\252&&\243\3154\232i4\322i\214i\231\244-M&\230i\271\244-NSR\003R\003R\006\300\247\3440\301\2523\306\003eEB\005O\0305~\002E^C\305=j\314f\256Fj\344F\257Dj\334mV\320\325\250\332\255F\325j3\232\231i$L\214\325p\333\032\256\305!8\305M\311\247\000GZp\257.~ES\226\250\313T\345\025\\\216i\361C\270\364\253\211\016;U\210\355\363\324U\330\241U\035\0056\343\n\274U\0227\034\323%<qU\230f\242aM\305(\024\307\\\212\317\272S\264\326[\020\246\2432zS\222&\224\360+Z\306\304\002\t\034\326\332\3425\300\250\244\2235]\230\323\r4\221L&\243-L-L-L-I\273\024\322\364\335\324f\2239\247-H\r<5;4\345jl\204\032\210(\31752\001Vc|U\244\222\245\r\232\263\033U\270\216j\354g\025r#W#5n3Vc5i\rZ\215\252\302\232y9\025VU\301\247\301!\007\025u\0375(j\007^+\313\244<U9MT\222\252\310*!\036Mh[\301\201\234U\225\213=\252dL\nq<{UY\316\343\201\332\242\333\305C \250\030Svf\230S\024c\212\215\306*\205\3361Y\023\250\'\345\346\231\024\005\210\310\255kx6\340\001\212\325\210\010\320b\202sQ\265FsMcP\263Tl\325\031j\214\2654\2650\265!ji4\204\321\272\2245(jpjw\231\212p|\323\303R\026\024\200\363R\253\001\324T\361\310\t\025e*\302\n\260\234U\250\232\256\304sWb\253q\232\267\031\253iVP\325\2045e\rH\r5\306G5]xz\275\031\310\340\324\240\323\324\327\226\311U$\252\262T\004f\247\267\203q\316+M\"\332\240S\266zP\303h\250\334\361\216\365Y\2074\323\300\252\322\236qQ\3434`\ncS0*\0311\315P\226#)#<T_b\000\363V\342\265D\000\342\234\355\260\361\214\323\321\313\032\224\360*\031%\n\t5J[\325\\\363U\216\242\t\347\245\037o\214\322}\241\033\241\243x=)\206ALi*3(\246\371\264\307\237m@\327\200t\250\215\343\036\224\365\272jx\270cS\244\247\034\323\213\023OV u\2517\232P\371\251\343\367\247\367\251U*\314d\216\265m=j\302U\210\352\354]\252\354Un3V\3435j3VP\325\2045f3S\nq\031\025VE(\331\02542U\260r*E\025\345\222\032\251!\252\315\315\010\233\230V\234\020\355\025c\024\207\201\357Q1\342\240s\324\324-Q\310\330\252\262\034sQ\027\003\2554\315\237jkJ=j\007\271\003\245R\271\272\003\241\346\243\206\350\036\365ed\337R\006=*\'\0074,\273?\n\251q\251\225$\003Y\263_\273\367\252\215#\267SM.GZa\227\322\201pGzQz\313\336\246[\364?z\246[\204q\301\246\271\003\221P<\307\265U\222V5\017&\246\215\t5m#\030\251V1R\252\323\302\212\177\035\351\014\253\234R\207\031\251\221\352\302\266MXJ\260\230$U\310\207j\260\023\214\212\2321\203W\"\355W\"\253\221\325\250\352\324f\254\245XJ\260\206\247S\232\220sL\221r*$\0305n3S\003^U)\252\222\034\324[rj\345\254\005\233\245i\210\266\212B\270\344\324NE@\354*\273\032\211\217<TL3\326\253L@\2522IP<\270\357U\236s\330\325y&n\325RB\317\234\322\300\010`+^.\000\251\2529\016;\325\033\213\225@@\353Y2\271s\232`J\016\005W\222AU\332J\211\244\250\332Jn\372U\235\220\360jU\275q\324\346\236/3\326\227\317S\332\236\222\'z\225$L\361VU\301\357R\006\003\275)\235W\2750\334g\356\320$8\347\232\003sS!\253\t\323\212\261\031\253Q\232\265\027QW\243\343\007\265[A\221NN\265n*\275\025ZJ\265\031\2531\232\265\031\253)S\255N\225*\322\260\310\252\303\206\305N\207\025aMyT\2035\027\226X\361SEhI\344V\224\020\204\035*f\340sU\235\262MV\223$\324,\246\242~*&5\014\207\000\346\263.%\3118\252N\365]\3335\021\353N\n\034`\323M\267>\324\242\r\274\212\235\034\255;\317\342\232\356XqY\362\332\263\234\365\250~\312GQQ\310\205\007\002\263\347v\006\252\263\032\211\230\324Li1\232pBi\336Q\243\3114\322\230\245\tN\013\216\364\360\017cR\2430\357Roj\006M<\034T\212\325:T\3501S\243m\342\254\241\315Z\214U\310\305^\217\221VS\245=z\325\230\352\344F\256Dj\334ua*\314f\254\2475a*e\251\224\324\202\241h\362\331\025 N=\351\352ppk\314\266n5f;lu\034\325\250\341\305M\267\002\241\223?\205C\345g\223\322\241p\240\361U\337\212\254\3475\013\266\001\315f\334\317\270\220\247\212\240\355P75\033\na\024\200\342\232\327\005j#r\344qP5\313\203O\216g|U\264}\243\236\264\3573\'\245)\301\034\325y\242\3348\252\022\330\263d\325\tm\031{Usl\304\364\246\233F\364\2446\305\006H\244\001\207j\031\210\246y\236\246\227p4\322@\243u=ML\206\244\342\214zR\212\221jt8\251\225\252\302|\334T\311\225<\325\350Nj\344g\326\257BF*\324~\2250J\2321\212\263\035[\214\325\310\315YCVR\254!\305XCV\026\244\007\025\"\232\\s\232\224\014\212G_J\363\310b\311\253\313\026{T\2420)\257\201P\2200Y\272U\013\233\241\321zV{\316{\032\256\323\222y4\323.{\325;\211\267p\275*\213\363P\260\250\210\250\332\231\365\250\335\2608\252\315\315>%\365\251\232\335[\266)R\035\2751N8\007\223L/\375\334\032\215\213\372\322y\244pM5\3468\342\253I.z\212\256d^\324(\311\245p\270\347\025VIQx\025Q\2432\234\203M\373&:\265\006\334\216\206\220@\304\324\211o\357R\224T\036\364\300rx\251\000\247\201NU\317J\225P\324\252\2652-L\203\025j2\017Z\267\020\002\256\304GCV\343\307j\266\225a\rL\242\254F*\312\n\267\021\253IVP\325\204\251\320\325\2045.x\245W\251\224\346\245^:S\310\334+\202\206,b\256\252`R0\252\362\270S\317J\314\274\273\317\312\275+-\344\311\252\362IPn\315#\023\212\256\365\003TMQ0\250\332\242j\256\346\241=jH\363\326\244iO\255F\323\036\334S\001$\362i\031\310\350j=\355\330\322\035\346\242e\220\372\324N\204\014\271\250|\345Rr*\031/\030gh\305T\222\351\333\275W23\032\2269\034p\rN\245\217Zx\311\247g\024\231n\302\201\0339\346\245\020m\353N\tO\tOT\"\244\037J\225@\251\227\035\252U\251S\212\265\031\253Q\232\271\023U\310\332\255Fj\312U\250\371\253\010*x\3705r3V\022\254!\253\tS\245H[\212@jTj\262\255\305H=\253\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\026IDATx^\355\335\333\266\243(\020\000\320^\231\377\377\344\311\232\311\255\317\211A\243\010J\301\336O\335&\022\254*\020r\351\376\363\207q]\247\007\000\000\000\350\323ez\000\000`\216w\216\001\000\000\000\000\240y\276\007\320;\037\330\000\000\000\014\3046\037\000\000\000\002\261\221\357\231\354\002\000\000\000\000\000\000\020\224\237\247\237O\016\000\2003\370\352#\000C\370\262\351\256}?\254\335>\000\000\000la\237\272\326\2277\024\2003\031\240T\241\260\000\200\026\014\277&\271\016\037\001\250\301\300\002\000\200q\244\277\022p\271\244\217\263\235\035\026\000\000\020\200\255K\232\270\220\2440\350\225\332\006\000\000\000\000\000\000\200\306\370y\017\000\300D\344\005\222oj\026\220]\000\201\242\237}\215\335)\023\211@\251\007\240Weni\014E\321@P%\266\037\327\177\247G\3063\356$x\235)\242q#RV2\270-\222pT\001\037\224\004\014\305\220\007\310Sl\323\227?\021\347\237\311\016\037a/V\nG\t\327a\000\240\214\217e\014\000\000\0004\315Nv\007\237\005@\017\214d\000 \213\275\024@\030\246l8\235a8\250\265\211_\373<\000\000\200\265|\017\000*1\270\000 &o\304\303VF\r\0000 K \000*q\2139\316\330\261\366I\346\237\177\246\007\016u\271\304*@\005\223ic\340bU\305(nY\t\233\231\260\035o\207\020\016@\222a\025Ce\233\237E\340\306\345`\253\024\300\031\352\025\317\346\226\317.\200g\2077\367\033\316r/V\025\013\035+6\300\2135\304\261v\254\215\2469\23745}\030\350\336\216\t\205\346\310\346\300\336\223\177\335vCO=;u\254\022\205;c)0\357\371\271?\363\357\323\227\316\333\250`S\354\224\316E\372\350 \016\234\2456(\220\222\307{z\253.\257\300\253\235*z\377w\233\317\362\374#w\303G\256o\337\322\373\355\361\275\276T\037a\234\236\311\332\245\332\261\323s\027Q\007AK\r\231\313\333\205\335\277\352\237zZ@M\\F\023\235xIv\246\334\257;\236\205\264k\240\354:y<\305r\307\010\272\036]?\027WnJ\353\315\257\002\350\272\026\000xi\366\226X\362>\324\354E\236huLJ&\342L\253/x \367\334\326\nLf\273\275\324\333\350.\271\005\020\226\312\205\035F\2330\000\206\222\275Jrw\250Fh\271d\017\3142\376\326\340\311\375\240\0029\005\240\tV\274p\234\357\343\355\3733b\n\264\364m\246\253\315t\204|\257\001\375\1772o?/~\3765\177\230\'\213\"\2779\340T\311\021\r\000\014\315\372`@_\222\036{\303\367\345\342z\365\223\264\330\351\353\234\344\214\315\017\3449\314\255\326\006\275\035\002\360#\275\364x\334 \334&\016\220N@}9\257\233s\016\007\311\034\255\263\247\315>\300(\214\367C\031q\000\000\235K\254\257\217Z\003\336^:\361\362ws\307\227\034\325\357~\345D\035\000\212r;?E;ao\247\'\024$\255\300K\353\363\201MqE\333\223/\035\345Mc\272=+@H\323\301\177\214\274)&\357,\032\2646\225k\237G\001\347\314\005\234\312\010\033\314$\341\341\006\275\202\355LN\005*\202\200$\215\202r\346\r\326\020Y\240M\226\021T\244\274\356\002\205\241\314r\245L+\r+\221\321\022m\324\327}*\363\304H\036\000\000\300\273\373^\346\366\357V\333\353m\323\326&pO\366\236W\262\247\t\222\202\204\364\270R\016\022\020\000\030\213\033t1\307\255\252\226Ii\023Z)\207\251\352\375R\177\215\223\240\261]\025\300\030\344\231e\325\027\003\023\275Wd\357\327GS\224\333fG\317xU\225\310\177W\001\001\322\322\003\3752\367\000\235\221f\036T\302\340v\177\003\354\262\343\\\032\223\223\312\234s\310\'\336T\245\300:\363\270\307_[^\353\265\334\267\320\226\003[r\250\337~I\002\214hy\236!\214\334D\346\236G\'\024\000@\024\211\031\273\3517\t\250\344\261s\177O\275B\030\234\002(\372\366\0304\250B\211Wh\222\352L\367\274k\254\"L+\301\224I\330O+e\332[\322X\305\237\353\025\214\374\240\024M\235w&\026\255\213N\201<\254t\334+\361\260\256\002\310\"\270023\000c\353\177Ig\214\337\t\303\340f\n\340\232\271\007\337=q\354n`^\305\246\343\232I\263X\3018f\246\001F\241\000\000\370\253\231]@3\035\031\324\001?\370\336\370\n\326+\017\033\303\026@\177W\004\001\031\210\320\260R\003t\266\235\331\007\0060\304\002s\335E\256{\026\364c\300\251\3170\347I)\3600\340<\310/\362\017\034\312\244\003P\313\3103\254\315\355J\267\"y\025J\253A\363\177R\325\326j\346\001\000\0006zno\326\356r\326>\017\000\200#X\235\215\300\'>\207z\206\273\335\261\365\3363\325Q2S%\333\002 $\267\002\000\240+\3365H\213\037\227\330\313\326\370\361\247/\261\307\023@\014\346Z\232\024bY\372\326\311\020=\216\342o0MP+\tT\033^\225k:\330+^\004\357\377\213E\274n\003\005\271\027\227 \212P\2261\005{X\3363u\275\225E\370\302\010\177\001\'\022;\240W\366\r#X\270\213)\000\200w\013S\346>U\032N\314\342\211C\213\266>\277\204\353\031/\332\233IA\025\ti\221FV\2512\034\006\261\030\273\305\007\233\361\253\320bt\270\005\223\321\371\036\270\2310~\214\350\327\201\217\007hV\361\\\335\277\330\004\204\264yB\230\216\367{\003\323\2034\346\232\221\351:\224\312I\236\201\377)\203\234L\334\316y\235\367j)\247\035\372qidf\341`\006>\360\303\2140\n\367\374\215\014\r\030Q##\377\327\367\320\357\223\267\217oZ\223*\224\337\331I=\276\305[\332\211D\326F\267\267\002\366N\036@4\257Qo\364\277\023\217\256\364\234\316\2757\376hF\273\336\257\366\006\344q\376\3077\345\247\177/-{L\326\356\330\313\257\016\036\365\222\247h\372\342\026\252\244\351~\227\262p\375\364m\210\372\246\032\365\003\301\275\177&\367\366\207\rr\316iC\334\236\307b\235\231\253v\205^sr\263\241S\031\2553\006\245\001\000\000p\246*\273\262*\215\022\301\206\367\212\032sV\321\306\215X\302\245\257\313\201\263\2345\037m\027\247\247\241\010+\320,\377\034khU\263W\265\361z\202v\273\025\327\217\355\354\256o\232U\2605\301[\237\017\254\031\354FV\347$x\237\250\361{\374\3703j\357\331l\315l\017\324\267b\3325\\w[\021\345\272\016\313\341\351W\312@\016+kj*\232F3\320\340\024\300\331d`\352m\206\353?<>\351\345\215z\340TE\227\230\305\233\343\253\3523\310\363\277\240\370\370\247(\327\252\336A\362\345&\265\260 \353\242\030\275<Y#%\005\361\230a\000\330\343\324E\330\334V\261\263\233\333\221\2273FDYo\246\"\200.|\033\341\337\036\007\000j\330\260\375r\263\036\333\206R)h\356\235\030 \251\346@\255\3316p\276\364\030O\037=\312\271\257>\246I\314\367\245 \347\354\337\337\251\3139\177L3\221Z\273\216^\216y\342\320\321\326^\010o.{\343\266\367\374%\305\276=[\252\235\n\372\330\311\026\313\324/\345\002\363\210q\271\366\272\262=sa\003\031\266\343Um/\200C|&\253\321\216\306St\t\377\'\225\252]\n7G4\351\002\330]\246p\223./\226\030|\203Z\231\370Rc*\361r\245\232^\345\320\027\013\344\371c\371\300\266\365\277\306\033\032\215\032\347J\001\236\252M|\026\021P\201\201\005g:{\004~\274\376\307\201\031\033o\366\033\237\036\337\177\034\3615X\n\320m\016\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_height_map_assets/tile-5.textpb
new file mode 100644
index 0000000..0cb5489
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-5.textpb
@@ -0,0 +1,3 @@
+tile_key: "5"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\325\210r*\326\332c\257\245S\272\203zq\326\271\353\244*Nj\203>*&;\272S\243OZq\217\212\253\"Ug\025\021\025\033.*\"\265\023\212\254\313M\021\344\323\230m\340TMQ\363HN\005D\304\323w\221NI\260y\251~\320\244t\250\245UpNj\250\201[5\024\226\313\216*\224\220`\324~MH\251\212\260\210jeLv\247\010\363\332\236\"\245(\007|R\205\007\2758*\216\324\274P>\224\360=\251\340T\210\2652\n\235\026\247E\253q\255Z\215j\344kW\"J\267\032U\224\\U\204\025:-[\215j\312-Y\215*p\000\247n\354)\342\244\002\245U\251A\342\245^+\224H\360j\310N)\205EB\351\3075\215\250\332\203\222+\233\271\214\2515\004g\346\346\256*\361JEV\225j\233\255DF*6\025\003T.*&Zh\030\2465D\334\232i\024\302)\214\265\023-3i\245\333A\004\212@\215\332\227\313\343\232i\266R3P\274\000S\004C5:E\236\3258\207\003\232]\241z\322\023L\306\343NU\003\245<&z\322\371b\236\020R\355\245\013R\252\324\252\265:\n\260\213V\243Z\271\022U\310\222\256\304\265i\005N\202\254\"\325\230\326\256D\265e\026\254\242`PF)V\244Z\221y\251\207\025\"\325\210\327&\271\245^j\302\307\305F\361\324.\244\203\212\315\271\214\220w\n\347\357m\216N\005d\264e\036\255\306r\005+\n\255\'5]\222\240u\250\030TL*&Z\211\205FE7mFS\232F\\TdSJ\323\nR\010\251|\272M\270\355Ml\343\212\210\356\244\332\306\224D\307\255H\260\016\365(@:\n\010\250H$\346\223\031\245\013O\tO\000S\250>\324S\224T\2503S\252\324\350\265b5\253q-\\\211j\344b\255\304*\312\212\235\005Y\215j\324kW#\025e\026\254\252\361\315F\364-J\24352\214\np\346\254D\274f\254E\367\253\237)\212\221\007\024\025\315@\311\203Un\242\014\206\262\036\000\344\202+\026\356\334+\236*\262\246\332q\034T\016\265\023/\025]\326\253\270\250XS\n\324L\265\031ZB\224\302\224\306J\217m!ZM\224\340\224\326\300\250Z\233\326\215\236\224\345^\330\251\004g\256)\3338\351M)L\332O\322\232\313\212hQ\353K\201J\0058\n\\R\201O\013J\026\246E\253\010\265:-X\215j\324kV\343\025n!W\"Z\262\213V\021j\314kVcZ\266\202\254\247\025)l-E\324\323\305M\030\251\324qNQS\257\240\251\342Z\306)\236\324\340\230\024\205j\027L\325k\205\371Mf\204\371\3533P\207\014H\025\224\313\212cTn8\250\034Ug\025\003\255FR\230R\243d\250\212\321\266\223m1\222\230c\246\371t\322\230\246\232\211\206j\"(\013R*\342\237\200)~\224\264b\243lTE3\336\223\313\367\247\005\024\360=\005;fz\321\263\024\273i\333qJ\00752-XAV\021j\302-Y\214U\250\305\\\211j\354kVQju\025b1V\243\025j5\342\247QO\3054\256\r9EJ\234T\353\315H\242\254 \253\n0++m.\332aZ\211\205W\225A\0305O\311\352k#P\307#\275bH\274\232\211\2050\256j\'Z\205\324T\014\242\233\260\036\264\326\213\025\023GP4t\315\264\025\244+L+M\"\243e\250\231i\205i\205i1\212N\224\233\21581\247\002i\371\310\246\220\r0\250\244\331\232p@)\352\276\325 L\212]\224\334{Rn\\\342\236\005L\202\254\242\324\350*\302\n\263\032\325\270\326\256\302\265u\027\212\235\026\247E\253\010*\312\n\262\203\212\231jA\3158\256G4\320*U\251V\247\214U\250\327\035idp\200\346\251\021M\"\241\225\266\203T^\340\346\2432\231\010\024L\010\210\342\260n\306\342k6H\352\006\216\243d\305@\365]\306j\026\024\312z\034\360i\255\035@\321\324L\224\320\264\205i\245i\205j6J\211\226\241aL\"\223\024m\244+F\332p\247\204\310\250\330\020x\244\301\247\001OT\024\360\230\245\342\223u79\2441\344{\323\343\007\241\253(*\304b\254\"\325\210\326\255\306\265n5\253\221-\\AV\021jtZ\235\026\254*\324\353R\255J\264\343\300\246\255J\005L\213\221V\243\000\n{\316\261\257^k*\352\3739\301\253\246\230\307\025\237r\3475E\315\020\374\315\305Y\237\0011X\2271\362k>D\250\031*\274\242\251\3109\250H\246m\3155\242\250\310 \324\313\363\255F\361\324\r\035G\267\024\025\2462\324dTl\265\013\n\211\226\230V\223e.\312M\224\205iU1Noja\244\300\245\340R\206\024\027\250\313\023N^jUZ~\336iv\340\203S\3063V\221j\302-Y\215j\324kW#Z\271\032\342\255\"\325\224Z\235\005N\202\245\025*\232\221j\314c\212k\036iTT\261\256ML\\ \252\322\337\204\350j\224\267\245\272\232\253\270\312}\253\245<TN+:\340u\252R\016\rE\014\233d\253\023J\010\254\371\271\252R/5ZU\252r\257\006\2522\234\324e)\230\307Zi\344Te\t\247\306\2705#G\232\205\343\250\032:\214\246*2\264\302\265\033-D\313Q\025\246\225\246\355\242\220\212JF8\351Q\223\2323HO\245!\367\244\335\216\224\231&\224\n\225EL\2434\374`S\212\344\212\261\032\020\325m\026\247AV#Z\267\022\325\270\305\\\214U\230\305X^\005J\225:T\303\255H\2652\216jq\302\324}MJ\253S)\300\250g\014\340\366\025\223pB\344f\252\002]\272\325\350\227\000WHG\025\024\230\000\326e\311\347\212\244\307\255V\301\335H\354sP\2775\013-W\2213T\344CU\335@\250\231sQ2R\010\271\247\210}i\255\026\332P)\032<\212\201\243\250Y*\026LTl*&\025\013\naZc-0\256)\244SH\244\305!Zn\332n(\013\232k!\246\343\024\n\225EH\005<S\327\222*\310\2178#\265N\203\326\254F\265a\026\254\242\325\230\305Z\217\265Z\216\254\245N\265:\n\262\202\237\212\221j\302\017ZWn\302\221j`jU\246J\245\201\305a\336.\3065\005\270\346\257)\342\272\031$\n*\224\323\0228\2522\261&\252\266i\270\3438\250[$\323J\323\031*\007J\257$UY\340\315@\320\221M\020\372\323\274\254R\371u\033\246j\002\204\032Q\356)\0320j\254\221\324\014\276\265\013-D\313Q\025\250\330S\010\246\021L+M+I\262\215\264\302(\t\232x\216\202\242\243(\r7e=V\245\013\305\033i\350\2705r>EL\253V#OJ\267\032T\352\270\251\343\025j5\253(\270\253\010*\302\n\262\203\212\235E>\236\202\246\007\002\230[\232z\232\2206)\301\352@r+\013Q\220\0075\005\261\310\315\\\007\212\336\237\322\2522\325y\022\230\2109\334)%A\216*\261\213\322\233\345S\035p*\253\014\232c&j\026J\211\343\250\374\272B\224\335\264\326Z\201\326\230\026\215\265\014\261\367\252\256\225\013%B\311Q\262\324,\264\302\264\302\264\302\264\233h\333F\312iNiBb\224\214t\250\230\032m.)B\324\200R\201O\013\212\2263\203W\023\006\254 \003\025e\0179\253\t\311\253Q\245YE\253\n*t\031\353VQj\302\216*@qN\0075\"\323\213qH\274\365\251\001\002\223\314\317JUl\232\260[j\023\355\\\276\2416\351\210\007\275X\265\030QVI\256\226X\362*\233\251\007\245!\213\"\242h\366\324.3Q\354\246\262\325y\026\2520\301\246\221Q2\323\n\361L\333M+M)Q\262\324.\225\036\314Q\266\221\223\"\251\310\2305\003\255B\313P\262\346\242e\246\025\310\250\312`\322\024\246\354\240%.\332M\200R\025\244+M+\232aJM\264\340\264\354R\201O\307\024\016\r[\205\262*\302\236j\324F\256\306*\334hx\315[\215*\302GV\021*T\03056q@\346\245Z~\354R\016z\322\226\240\232E5*rE\027\223yp\266=+\224i\014\267\034\372\326\274<(\251\031\253\257a\232\205\343\250\031j\027\031\250\nS\n\324l*\254\203\255Ve\346\230V\243e\246\021L#\024\230\244+M)Q\262Te)\214\265\031\340\3242\307\273\232\252\311\216\265\013GP\230\352&J\214\2450\255&\312B\224\233x\246\355\305&\332]\224\326\025\031\\\323H\244\305(\024\270\247*\324\230\243fj\304K\267\255Y\215sV\342N\225v1W\"<\n\270\235\252\302\324\302\235N\025\"\324\231\240ry\247\026\002\214\323I\315*\361R\306~a\212\253\253I\266\023\315s\226\243t\244\326\322\034\n\031\253\271+\201P\275Ts\203Q\036i\254\264\302\271\025]\324\203P:\346\2532b\242e\305F\302\243\333C-G\266\223h&\224\255FV\230\313Q2\324\0169\246{\032\202T\364\252\344Tl\242\243d\250\231*\026Z6\323J\321\266\232V\233\212R8\246l\3157m\006<\212h\212\235\345P\"\346\236#\245\331\212U\0375H\300\361\212\265\000\343\232\271\030\253iVc\253q\0360j\322\232\225i\364\340i\341\251\331\244-\212\001\247\nu(\251c\342\262\365w\314x\254\333(\361\315h\203\201Lf\257Aq\305Wq\305A\345n&\230\320\343\221Q\225\246\025\250\035*\006^j\007LT\014\242\242d\315&\312aZ\215\226\233\266\220\212a\024\3223Q8\305@W&\230\313Le\014*\007\212\240d\305D\313Q:\324%h\333HV\200\264\2333L1\322l\243e\'\227HR\224%8&i\342:_.\232\313I\262\236\007\255X\217\002\254F\325j3\232\267\035Z\217\203VS\232\235)\304\320*A\357J[\322\223\2558\nz\323\361\305\000T\313\302\223X\232\223y\222\005\024\220\307\261EHMB\355^\222\303 \324\016\264\213\036\325\317sQ\225\354j\273\256\323Q\232\211\305Us\203P\310\300\212\254\335i\204Rb\230\304S\010\244\333M S\010\024\302\265\033\246EWd\305F\302\243#m5\230b\242!Z\242x\275*\273\307\212\204\2574\233h\333M\305.)\n\342\232\027=\251\3333I\262\223\313\346\202\224\233qO\002\237\266\230R\220\2554\014\032\231*e\340\325\310NqWc\251\322\255F*\302\234\n\\\346\234\242\237\264\321\212p\024\340)\302\236:S\221w\032m\324\302\030\311?\205c\'\357d.js\300\250\231\252\006j\364\362)\247\2574\322sQ8\035\252\274\303\"\253g\326\230\334\325i\227\212\250T\346\230V\230V\232EF\313I\212c\036\324\303HE4\212i\034T,\274\324\016\274\324L\271\025\023-DS\024\231\301\250\244\346\253\224\346\223m!Zi\024\230\247\000\017ZpP)\017\024\302=)\240\034\323\266f\217.\224/\265=V\227m1\226\232#\334x\253P\301\201\310\251\032\014\364\247F\214\235E[\211\263Z\021/\002\254(\305<t\247\n\225EH)qF)@\247\201O\305=\230D\204\232\302\274\2717\022\355\007\212\2225\n\242\2075]\315B\306\275T\216*2)\206\243j\211\207\025M\324\202qQ1\250\233\232\201\222\243e\250\230TD\021M\357JG\025\004\213H\007\255!\244#\212\211\2526\250\335r3P\225\305D\302\242a\305D\303\025\023S\010\244\305\005j2\271\246\225\244\305\030\315\000{\323\200\035\350\300\317\025\"\256i\305@\246\021@\024\255\300\250\031\262j\325\274|d\325\221\307J\221\005X\010\033\265H\220\000sV\221p*e\247\201N\002\244SR\212Z\\S\200\251\000\247\242\3675\233\251\334\355R\252k2\3352w\032\267\234\n\215\332\253\273T,\325\353#\2450\212i\024\302\264\307N*\253\2575ZT\364\250\010\2460\250\234T,*&\025\t\353G4\204f\233\214Rb\2028\250\312\372\324r(=*,qP\270\250\030TL*\031\005E\212M\264\233qA\034SqMaM\305\0053\322\233\267i\245#\212j\360j`h\316iqN\013Mu\250\322\034\265]Q\265p)\300T\350*d\030\251\324\324\252ju5 \247\001N\3058\034S\201\247\2575*\255H\005\023H\"\214\375+\234\271\223\316\227\216\225$ch\247\026\250]\252\006j\205\215z\332\236)M6\232N)\255\315W\221j\254\225Y\226\243aP\275Dx\353Q\265@\303\006\233\232v)\204sJ\005\014\274S\nqQ0\305D\303\212\256\342\241aL)P\262sQ\262S6Rm\244\"\233\212B\264\335\224m\244+\232n)\n\322\212u:\236\2640\317JX\306*QR*\324\350\206\254(\002\244\003=*E\025*\324\313R\001K\216)1\353R \251\2213S\204\307Z\216i\204JI\254K\313\343&@<UhT\236OZ\261\322\243f\250\035\252\026j\211\232\275q\rJ\005.\332\215\226\231\266\241\220u\252R\214\032\200\232\211\352\023P\311Q\324L)\201y\2511\3054\255 \247\036E4\257\034T2-@\313P\262\324,\264\312C\036\352\211\243\250\312\323\n\324dSh\244\315\035i\n\321\262\224GA\217\035\250\331F\332P(\247(\251TT\3121Rg\0254{Oz\235F:sR\nz\232\225\016jQ\322\2274\001\223S \253\000\205\0243\340Vm\365\332\200T\236k\025A\221\363\3335mF\000\241\215B\306\240cQ1\250\311\257\\\214\325\210\316MH\303\002\243\316i\255PH*\234\313UY9\250\331j\006\025\003\365\250\311\301\244\"\223\024\206\222\216\264\230\245\003\212\212E\250\035j\026\025\023\n\211\222\223\030\240`\365\2441\203Q4 \364\250\0319\3057\312\246\262\342\233\2126\322\355\247\010\351\341qHE7m&\332M\264\005\247\205\247\201R\n\220sO\013\351R)+R+\023S)\251R\246\335\201M\315H\206\247CN.;\232\257qp\241\t\006\271\371\244i\245\340\325\230c\332*S\305F\306\240sP\261\250\231\2522\325\353\360\363\305]\2120\0074\222\361\322\242Z\030qP8\252\322\214\325r\265\033-@\351\212\256\351U\331i\240Rb\233E(\024m4\355\230\025\033\255B\313\232\211\222\242d\250\312S\nSq\212i\024\303P\260\346\220-!\2174\323\035\001)\302:v\312B\270\246\342\215\264m\244+@ZP\264\340)\300T\212*QO\247\255H\225:\320O4\242\246Q\305H[\002\253\317\'\312H5\213$\256\354FN*h#\000s\326\254\343\003\212\215\315@\355U\335\352\027\222\241/\232aj\366H\276Y1ZJ\006*)y\250\361KP\270\252\356\271\250\031j2*\'L\324\016\225\003G\232\214\307Ld\244X\211\243\312\346\234#\247yt\245*\'N\265\\\2550\250\2462T,\225\031ZaJiJc%D\311H\026\224\241\246\355\366\245\tK\262\223\030\2460\3153m\006\234\007\024\005\311\245+F(\305(\024\361N\025 \247\255J\275jaJ94\354\201N\rMy1\221U\244\311\3435\003\332\356\031\217\357TA\214M\265\3705:\311\221H\304T.F9\250X\002\274TE\021\201\301\250\377\000v\271\365\246*&I\'5\354\261\304K\364\253d\020\265\016\t4\270\244\"\243~\225]\373\324,)\204sLe\250\035j\"\224yY\246\230E\'\226\000\243\313\024\276]\036]F\313\351Q2u\315@\311L\331M)Q2TL\224\302\224\323\0354\2450\305\232o\225K\345\323Lt\335\224\036\005BFi1\212i\031\246\260\245\035)\300`PM&(\240u\247S\305H\265\"\324\240T\203\245&H4\240\344\323\267b\240\335\311\240\234\322+\355<Uk\245\017\363\n\254\222\02585`>i\216\001\006\240u5\001R3Q\0255\031\316+\337\002*\364\246Hs\322\242\034Rri1Q\275Uzn3Me\246\021\232\214\246i\246*\004t\326J\217fivb\233\232v3\322\243(ED\310MD\361b\230c\366\2464u\023%FR\231\345\321\345\346\232c\246\024\305\n\240\236i\216\203\265DEFE0\256i\245qM+\353Q\2650\323\227\2458\n1HE6\212p\247\212\221jE\251\226\226\202h\034\323&|\016*\020\334R\202i\245\251\205\263PN\230]\303\2556)2*\\\344TmP\265B\302\230\302\275\325\230\223M\'\327\2553u&i\t\357P\271\315Wzh\353JFi\204S\225)JRy^\324\306\212\231\345b\230c4\301nX\346\245\021\000\005\006,\324f\034v\250Z,\236\225\033G\212\211\222\242x\352\023\035/\226)\245)\214\270\250\034c\245F\006)\255Q\225\3154\256)\207\330S\010\250\332\242aM\"\225y\247\n\\R\021Q\232QJ:\323\305J\274\324\202\236\016)wQ\234\323\272\n\212R\270\371\215D\030R\027\250\313f\231\234\032\224\'\232\244\032\240\352`\223\025:\234\214\3225D\303\232\211\252&\036\225\356\254\270\250\3150\232@i\030\346\2425\023\nh\034\323\261I\212\231Tb\236\020\032R\243\025\013\255Bh\t\232]\270\024\320\271\346\235\266\215\240\324m\030\025\033C\270dUW\214\203\322\242)\236\325\031\216\233\266\243a\315E%@\313Q\225\246\354\246\025\364\2462\201Q\265D\334\324dS\010\246\021M\007\006\244\352(\315\004\323\0174\200S\200\245\251\024\342\245\rN\315&\352p4\342\340\n\241p\344\26501\247\346\223v)\240\345\252h\337k\324\027\250X\356\250\341l\255Hj&\250\230TF\275\325\352\006\250\330\322f\2239\246\236\365\031\244\305;\024b\236)\340\322\265F\336\365\036\334\322\343\003\212M\271\247\005\243m\001)\031j\"\010\250]7v\2506\020\324\311#\347\212\205\223h5Y\205FV\243+M)L`1\357P\267\035*\027\250\2150\212i\\Tl2j2\264\303\301\245V\305;9\351Hi(\006\2274\264\345\247\212v\352\\\322\346\243w\305T\221\262\324\253N\3155\215,JpX\323\315%\313~\344\343\232\243l\374\220j\331\351Q\265D\325\021\025\356%\270\250\332\2424\224\235(\3051\205 \024\360(\333K\266\234\0058\014\324n\231\245\013\305\0333K\263\002\215\264m\315;g\024\306\002\242+Q\262\324,*6\030\252\262\014\324\016\270\250\312S\010\002\243`*\027\305@\325\013\323\n\036\246\223\201Q\261\346\2439\246\032\214\212f9\247)\245\315!\244\024\242\224\361NZ\226\212L\320\0335\023\344\344\325|d\323\205:\232\3252\377\000\253\000Si]wDEe\257\311.*\3709ZcTmQ5{fsHi\270\246\221\212JP)\n\322m\247\001N\002\214S\202\323\266\342\223ni\3018\244\333\212B(\3058-\0140*\007\025\031\366\250\230\361Q1\250\217\275B\342\253\260\346\243aQ7\025\003\232\205\215Dj&\034\324m\222i\245x\246b\230E1\205FE4\255&1A\244\315\002\234\264\032U\342\245\007\212\t\246\232\024\363N\342\253\310\230<S@\247\001H\302\244\204\344m4\2450i[\345^k\036f\377\000H8\365\253\310~QCTL*&\025\355C\255:\214SXS@\247\205\245\331K\262\223m.\3326\323\302\320iB\323\261HE4\255\001i\341x\246\270\252\356*\006\030\250\232\242aQ\270\342\240l\324D\376\025\023\232\201\315@\365\023\n\214\217Jcq\365\250\210\3151\251\270\342\232\325\013\032a4\334\321M<Sz\236(\247\n\\\361H\r<\032\\\321J:\322\323Xn\250\312\021H)q\3054\214\034\212x\224\367\243%\301\315d\314\000\270\343\326\264#\037(\241\205D\325\033W\264\201N\305(\024\025\315\001)\352\224\375\264\273i6sMd\247\204\244aH\251K\214R\342\220\255&(\003\232\220\014\212\215\305WaQ0\315W\224\343\201U\036LTfBi\205\261\326\243b\010\340\324\'\223P\311\317J\200\323\010\3151\205D\302\230ED\302\232x\250\336\241j\214\322\037j94\323B\014\320\303\024\320i\335\251)\300\323\201\247\nu)\034Rc\024\244pj\035\264\264\021M#\024\341\367X\326C\374\323\3765\245\030\371E\rQ5D\302\275\245\016E<\014\322\201N\0034\360\264\365\030\024\354f\200\264b\215\242\215\264\2052i\312\264\215\035.\312M\224\233(\330\007Zq\034qP\270\250\030T\022p*\204\362c\201T]\2114)\315+.F* \204\032G\025]\315@y8\244n\005Bi\246\243aQ\265D\306\241sP\261\250\311\246\203\223Rf\232}\350\r\203C\220E0S\373Sh\025\"\212x\247\001N\002\227\031\243\030\250X\037JA\326\235\212i\024\262|\2201\365\254t\033\246\255$\351CTL*&\257dS\201S!\315J\0059E<\nv3N\002\227\024\241h\333H\306\221Fj@\264\355\264yt\205qH\026\215\264\204Uw\025\021\\u\252s\347\234Vl\334\036j\253\002\335(D \324\342>3Lu\364\252\262\034\032\204\340\217z\201\206\323Mc\221P\2650\232c\034T\016\325\0135B\315Q1\250\3157q\355N\317\255\004\323sN&\201KE\003\255J)\302\244\002\224S\300\245\307\265\0057\014Ur\273X\212\\R\005\311\250\357\316\310p+2\325r\371\255 8\246\260\250\232\241j\366E\351O^\rZ^E<\n~)@\247\001N\240\361I\270\na\303\037JT\0252\212\220\npZ\ng\265FS\024\230\2467\025\013\016\365\003\325g@A&\251=\276\366\366\250\336\334/j\213\313\013A \n\205\330b\263\356\034\202qU\274\323Am\335j6\250X\323\013TL\365\0035D\306\242cQ1\246\026\246\347\232]\324\205\361M\337OBZ\244\351M\007\232x\024\240sR\250\315;\030\247\212p\024\360)\330\245\002\253\310\270z1J\253\315S\324\217\312\005U\264\\\n\275\216)\214*&\025\013\n\366E\034S\300\253\021\2361S\250\251\002\322\355\245\305\007\000f\243-H9\316i1R\240\251\007\024\340j\302-+\n\214\212\215\252&\300\250\034\325g\3115\003\324y\002\241\221\252\254\215P\263T\016\325J~k>F\330jDp@\2476*\007aP\267\265Ws\212\205\232\242g\250\232J\210\276i\205\251\241\251\333\2513M&\237\033T\244\346\205\034\325\2208\243mH\243\212v)TT\200S\300\245\305(\246H\2319\246\355\247\204\302\223X\367\255\276B\007J\222\336<(\253\030\250\336\241j\205\253\331\224qN^\265\"\037\232\256 \310\251\000\245\305(\025\034\265\0363O\2152y\247\024\000\346\234\007\034R\323\324U\244\340R\265B\325\003\236i\2147\003UO\007\223Q;dUv\340UWl\032\205\3375Y\316MD\306\240sUf=k.v\353Kn\341\207\275H\357\212\201\244\006\242g\250Y\263Le\315@\353\212\254\324\302p*2\324\335\324\340\324\271\246\026\247\304jqR\240\346\247\307Jx\024\360\264\355\271\247\205\245\003\024\361N\305.)6\344s@L\221Q]\270\2110:\326:\251\222L\232\272\213\201Jj&\250Z\241j\366\304L\212f9\251PU\250\316\005;94\345\346\236\005G\'&\221W\234T\244`qM\344\322\343\002\224\n\224\n\231\017\024\023Q9\250\017\255\034\021UYpOz\202CU\344\351Tf<\361P1\250\034\324D\343\255F\304\032\2550\3105\225t\207\223T\321\312\036*W\230\260\346\241\337\223C\270\307Z\256\317\317\024\3174\212C.j\0269\246\021\232\211\226\242<P\032\227u%I\030\346\254\245Y\215sVB\212P1N\024\341N\247\001O\002\235\2121J\0274\343\210\324\223X\367sy\256@\242\030\260*|b\230\325\013T-P\265{x_\226\205\2175 \\S\306E(52\260\002\227w\2457\2559\0079\247\023\305 \024u4\341\326\245\002\236:R\023P\261\315D\306\243g\307\322\242f\316qUdj\253#\325Y\rWcP9\305W\221\352\271\222\243w8\252S8#\004U1\215\324\222\201\216*\253\270CP4\271\351Q\2310i\215&i\236`\246\2313H_\024\201\367\036h\221x\315@z\323\205(\025i\024\005\367\253\021DO5r8\3609\025&1J\0058\014\323\200\245\002\244\024\352SH9\251\020`\363U/\246\302\355\006\263\242\217sd\325\300\270\024\326\250\332\241j\205\252&\257s\000m\241F\r<\n\\R\342\212QR\216\224\341\305\035M\006\222\236\242\244\024\244\340TL\325\016\356i\214j23PH\330\315T\221\352\254\215P9\250d8\252r\311T\244z\256\322s\305!\220\221\315V\227-U\330\005>\365\013\270\317&\252\315\317CU\217\006\242rsQ\226\3050\265\001\270\246;\322)=ju\223\214\032M\231\351J\027\024\340\274\325\210\306p+B \025j]\330\245\0074\372x\036\224\360\236\264\273pi@\245\244\357\305H\243\271\246\314\373W5\225!2\275O\034{E<\361Q\265D\306\241j\205\252&\257vQ\223O\013\315(\024\340\264\273h\333F\332p\024u\247 \240\365\240S\324S\307\002\230\315P;\324\005\271\241\337\025\031\224b\252\310\371\252\222>*\263\275WiqP\311(#\025FBI8\252\333Y\316)\031\002\216j=\303\322\242\221\205P\231\262p*\264\2101\235\334\325f8\353P\273T,\325\003\311Q\031i<\332\024\356<\324\243\212w=\251\351&\323\203S\207SN,;T\22075y\rH\005J\202\245U\311\346\247\003h\340Uw\224\253\363OY\225\273\323\203\002i\304\201M\363\000\245\r\232\212y21U\343\217\234\232\233\2651\215D\306\242cQ5B\325\023\032\367\244\\S\300\247\005\247\001K\212\\{Q\212gCG&\2348\315%8S\305)<T\016\325]\332\242\335\315A4\265\030|\212kt\252S63T&\227\025L\313\270\343\232^\325\023\020*\274\222\355\351\305Uy3\336\243/\357U\246\233\035\rg\315+)\252\257p\335\352\006\231\215F\\\232\211\336\240\221\352\003%&\363\232\225\036\246\017N\363\005*\266ML\246\245U\315X\210m5q*\302\363S%M\030\313T\300\214\32471\003\326\241\020\205\357FpqR/4\2059\241\334(\250F\\\344\323\372R\023Q\261\250\230\324Lj65\013\032\211\215{\341\247\255I\326\224\nu\024S\010\364\243\030\244\242\234)\340\342\243v\252\3625Wf\250\367`\325y~f\300\245\013\264TR=Q\235\305f\316s\234Ud\030j{6\005V\226N\0175FY@\357U^\340\n\256\367G\267\025ZI\262\0175I\3459\344\346\240\222L\324-!\002\231\347v\246<\234\324\022=W\337\315.\372\2266\315I\273\024\34595f4\316*`1S%L\255V\342l\212\267\030\251\2623R\306y\025ch\340\367\250\2475\016Gz\000\035i\305\325\005@\323\0268Z6\347\2559@\024\214j2\324\3065\023\032\211\215F\315Q1\250\230\327\320\000d\323\302\323\261N\024\242\227\024\224\323I\236)(\245\024\244\340T\016\365Y\336\241f\2463\0002i\261\214\363D\216\000\252\023\313\214\325\007\223uU\221\211\310\2507\001\326\241\232a\214\n\245$\243\234\232\314\232}\316pj\007\220\001\315U\226\340\016\365N[\237z\254\3679\357P\264\376\365\023OL\363rhy\006*\006\2235\021|\032O2\245\212Nj\312\275M\031\031\311\253j\343\034S\303T\252\365*\022\306\264aP\024f\254\007\300\342\234\246\254Fy\025h\0163\351U&\223\3465Y\244\'\245 v\247\034\221\315:5\301\251\030\342\231\272\232\315Q\026\250\331\252&j\215\232\243f\250\330\324lk\350UZ\220\014S\261N\013F\3321HE4\212\214\360h4\235)sLv\252\316\365]\332\242\3150\234\237j\014\201G\025Zy\2532yI\357U\267\032\206I\007j\252\357\305R\232oJ\245+\2229\252\022\270^\365Fk\214w\2522\334\023\336\252<\331\357Q\0313\336\230\362c\275E\277\336\2173\025\033L}i\206Zi\2234\201\351\351&\rYI\211\253Q\271\"\247V5\"\311S\243\325\313c\226\346\264Q\270\247\006\346\245SV\021\352g\230\210\316*\233\266T\223L\006\236)I\245V\346\225\332\230[\212\215\232\243-Q\263Tl\325\031j\215\232\243&\243&\276\214Q\305<{\323\300\247Rb\220\323I\246\226\250\330\212nsE5\233\025\004\217U\235\352\006z\215\237\002\253\313)\003\212\254\327\030\030&\253\3119#\255Ty\200\252\317?\275Wyrj\255\304\373W\255Pi\300\0075F\342\357\260\254\331\256I\357T%\236\252I-Wi)\003\323\035\351\201\263J[\212\201\232\230Z\233\270\323\303f\244S\315Z\216\255FqSn\251\020\346\254\306j\334\'\006\257\243qO\rR\253\324\310\365&\340\303\031\252\316\330R)\241\251\341\250\337J\036\202\364\322\374Tl\325\031jc5F\315Q\226\246\026\250\313S\t\257\243\326\236)\340\323\251\245\2050\2650\275F^\232Z\231\277\024\031*&\222\240w\252\356\365\003\275A$\234UYd\343\255Uy=\352\264\217U\244z\253$\225\001\223\255P\272\233<\n\241#\026\035j\224\307\025Bf\252R5Vv\250\031\351\276e4\276h\337\212i\222\230\315Q\026\2405=ML\255\212\261\033\325\204z\224=L\215Vcj\265\024\234\325\324\223\212\224=H\257\232\225_\024\342\374\361QJ\334\373SCS\203\321\276\224=\033\351\245\3522\364\302\324\302\325\031jajajaji5\364\222\323\3058PZ\243-Q\263Tl\365\031zizc5Fd\250\332J\205\344\250\035\352\274\222Ug\222\252\311-Uy*\254\222\325i%\367\252\317&j\031$\n+:\342A\234\3257\237\216*\234\262\016rk>iG5M\345\252\362IU\313\022h\372\232ajM\324\233\251\214\365\031z@\365*=N\255\221S#b\246V\251C\324\321\275[\216LU\204oJ\265\034\270\340\325\205|\364\251\221\252`\324\245\275)\254r\rB\037\236i\333\3517\322\357\243}4\2750\2750\2750\2750\2654\2650\2654\232Bk\351Q\322\234\r\005\261L-Q\263\324L\365\023=0\2754\275F\317Q3\324FJ\205\244\250\036J\255$\225VI*\254\222UY%\252\222IU\236Z\201\3445VY}\3536\346\343\255gIpMWyI\252\322d\365\252\316\r@\355P\263Te\375i\246Ja\222\220\313M2S\013P\rJ\206\254#T\352\325\"\265H\032\246\215\215[\210\346\255#b\247V\315X\205\271\253j\325 z\013\322o\252\356\330\177\255.\372B\364o\243}!zizazizajijijM\324n\257\245\305\005\261L/Q\263\324L\365\033=F\317Q\263\324fJ\211\244\250ZJ\205\244\347\255D\362T\017-V\222Z\251,\265U\345\252\262KUd\222\253\274\200\014\223Ud\234\023\305S\232oz\316\232\\\346\2523\n\211\210\250\235\352\254\222Uv|\324\016\325\0135FZ\230\315Q\226\305&\352P\325*\232\225jT\253\010jU\251\026\247\216\256F8\251U\252d&\254\306pj\312\275?}\033\351\013\324r\034\256Gj`~)\013\321\276\215\364\322\364\322\364\205\351\273\251\013SKSwQ\272\215\325\364\301jc=F\317Q\263\324L\365\033=B\322Tl\365\031z\211\236\241i*\273\311P\264\265]\345\252\322IU]\362j\274\257\201\326\251\264\234\362j\264\327\n\265\237,\345\317Z\200\275T\232J\243,\225X\311\232cIP\274\225RW\252\306L\032k>j&j\2179\240\324mM\247\255L\242\245Z\225\005N\242\245Z\225jd\253(ML\265e\rJ\255S+\361N\337N\r\232B\324\335\334\021P\206\244-I\276\202\364\322\364\205\351\273\350\335I\272\232Z\223u\031\245\335_K\026\250\231\3522\364\306z\211\236\241g\250\231\352&\222\243/\232\215\232\253\311&*\264\222\372T,\304\324.\330\352j\264\222UY$\252S\314{U\t\'#\275S\222]\307\255B[\025\023\311T\245\222\251H\3715\0136*\026z\201\344\252\362>j\271=i\205\251\245\251\231\2434\207\232LS\324\032\231EN\253S\"\342\246Q\232\225V\245U\251PU\2055*\361R\251\251CT\240\361K\272\234\255C5F[\025\036\356i\013SKSKSKRn\244\335K\272\215\324\233\2517Q\272\224\032\372I\244\250Y\351\205\3526z\215\236\241g\250Y\3522\371\244\006\230\355\201T\245~\265X\266M#\266\005S\226J\254\362U9&\252\222\310\rg\316\370\'\025I\236\230d\250\235\370\252R\234\232\256\347\025]\332\253\273T\016\325\0136j75\0214\204\322P\005.)\300T\252\265*\255L\213S\250\251UjU\024\354T\211S\251\251\224\324\253R(\346\245\307JN\224\3654\255P\267z\2046sHZ\232Z\230Z\232Z\220\265&\352]\324\273\2517Q\272\215\324\273\253\350\306\222\243/L/Q\264\225\013IQ\0311Q4\231\250\313\322\254\237-C$\231=j\274\244\036I\250I\013\316j\264\323g\201T\235\352\254\322\340U\007\233\223U\244\2275VV\3105U\372T\r\221P\263T\016\325ZCU\334\325w5Y\330\212\2179\2460\3154\203M\305\030\240\n\220-=TT\252\242\245U\251\225jUZ\224\nx\025 \025\"\214T\200T\212je9\251\343\\\323\363\203M&\234\247\326\206jc\237\2275S8&\202i\244\323\030\323\t\246\356\243u.\3527Q\272\215\324n\240\267j\372)\236\242g\250\332J\211\244\250ZJ\215\244\250\332J\214\2754\311\201PI)\035*\264\223\037Z\257$\307\326\253\264\231\315@\317U&~\rf\313\'5Y\346\250\232L\324L\325\023\236*\253\266*\027j\256\346\2539\252\356j\026\250\310\240\323M7\024\240S\202\324\212\265 ZxZ\221EL\202\245\013R\001R\240\251\002\324\212\264\360\264\340*D\253qc\024\215L\3174f\232\317H\355\362UV<\320M4\232a4\302i\231\2434f\227u\033\250\335F\352@\325\364+IQ\264\225\023IP\264\225\013IQ\264\225\031\222\232e\250\314\231\250\244z\251$\225Y\244\311\346\241y*\273\313U.%\342\263e\222\2533Te\361M\3633M/U\344\3475Y\332\241v\252\362\032\256\325\013u\246\363IF)6\323\302\323\302\324\201i\341j@\265 Zz\214T\312je\\\364\251Uj@)\340b\236)\330\245Z\261\031\342\203M4\225\024\234S\031\262\225\001jM\324\231\246\223Q\261\246\223M\315\031\243u\033\250\3154\265(j\372\t\336\241i*\026\222\241i*\026\222\2432S\014\231\246\263SK\340T\022IU\036Nj\273\311\212\255$\265U\346\367\252\223M\232\252\3075\013SJdu\250_\345\342\230Z\243v\342\252\310j\026\250Z\240j\214\2126\322m\245\013F\312P\265\"\255H\253R\005\251\024S\361J\005H\253S\3061S\201R\001K\212z\2558-(\034\324\212\010\247S\t\246\026\244?0\250\266\225\353\322\241\221q\310\250\263\212\t\244&\243c\232i4\322i3Fh\335Mg\244\006\234\r{\353\311U\236J\205\244\250ZJ\211\244\250\232Za\232\217<t\250\344\233\336\252I8\035\352\253\334\201P4\331\025ZIj\2735B\347\212\200\232\214\232ij\206S\232\203&\241\221\252\271l\324lj&5\031\024l\365\245\331M+@\024\354R\205\251\002\323\302\201O\013R\005\247\205\247\005\251\225*U\025*\212\220\npZp\030\247\212r\217Zp\024\204`\323\030TMQ\346\235\234\216i\204\216\207\245W\2210x\250\263HM4\232a\246\223HN)\245\251\013SsK\234S\201\257vy*\273\311U\336J\205\244\250\332J\205\244\250\232\\T^v\016MC-\317\275Sy\211\250\231\352?7\007\025\024\217P4\224\302\331\250\211\2461\246\026\250\\\324]MG\"\212\200\212\214\255D\302\230E\030\245\240\212LR\342\224\nz\323\305=EH\005H\005<\n\225je\247\250\251qJ)\300S\200\251\002\344\322\355\305\014*6\024\302\265\023G\3157n)\244S\010\310\301\252\262\251\006\230\016i\r0\323I\246\023L-M\315.isFk\334\032J\256\362Uw\222\241i*\026\222\242ij\027\226\253<\276\365\003I\357Q3\324O.\005W2\363N\336\n\324\016y\246\027\3057~i\214\325\0314\306\351Q7\265D\315Q\261\250\311\24674\314R\355\243\030\240\322\036\264b\236\0058.*e\214\021\301\247l\3059EH8\247T\213R-J\246\245^i\340R\212x\251T\323\210\310\244\333M+L\"\243ja\250\315F\302\242q\221UXm4\302i\244\324l\330\250\213f\233K\2323FiA\257gy*\007\222\240y*\006z\211\244\250\036J\201\344\250\032J\201\344\250^Z\201\344\250\014\234\322\211\251\305\363Q\26574\023Q\223L\3150\324,9\246\021\212\214\323\0174\336\224\271\244\315\035iqF\332z\247\275L\027\326\2342:T\241r9\243m8\nZx\251\026\244\025*\032\230\036)qN\035j@02ju\301^(8\250\310\024\306\025\003\212c\n\214\323\032\2435Ve\346\253\223\212\215\237\322\2429&\222\2234\231\245\315.h\025\353\355%@\357P<\225\013IP\264\225\013\311U\336J\201\344\252\357%B\317Q3\324e\251\233\271\247\2074\355\371\024\231\346\220\2650\232a<\322\036\224\316\325\013\232\214\232ni\t\246\021F)\312)I Rn\346\236\257R+\212\220\032z\276)\371\356)\300\323\251E<qO\006\244V\251\320\346\245\307\024\243\255K!\004\014T\221\034\nV\250\233\212\215\215Fy\2465D\324\303Q\266{Uv;\270=j\264\203\025\001\246\023\212i4\334\322f\212\\\323\201\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\nIDATx^\355\334\331\222\343*\014\000\320\251\374\377/_\327\255^&\023\307\361\0166H\347<ug\263A\002\204\235\356?\177\026=\336\037\000\310\3439\005\016\257\217\002?\014\214\340z\t\260j\265\222^\022\200Z\376\016\255.\206\330\\\272NO~\372\010|\231\313!\200l\254\224@\010&\263\334\304\037v\351u?|\346\274M\023\000\000O)J\2433\265#\001H\200W\237\306\374\260\322E\237\336C\034\263\361\235}\242\tki\013\034\021w`\265=\2435#n\002$#\337\001\350\325\236b\344\343k-\202\313\336\373\347c\'\002\000\000\320\250\367]\035\000deM\204-\334\003\200\027\351\006D\272\006\357\224\246\226(\222\010\355\366\326\243\335S\273\330\277\216\360\325z\010\312\330f\003i\002!\030\312\225\005\336B\310\035r\3330\0026\274\204\261X]v[kn;0\000\000\000\255^\r\264U\00463a@h\215\226*\244e\321\201>\030\253\237<\327\324x\335s\331\337\005\014\001;\017\000\310\300\325\025\000\2567\267\203\236{\374\220\242\037\226\200\222\000\340\020\313Mr\022\000\240\030[\022\216YX\214\027\236\242\031C\307\203\277\3373\377\344\266\377Bz\327q\271\314\362\\,\001\000\000\202[.\007\211gs\304\177_\270\371\365\304\"\360\331u\232\001\235\2366\274\221\311\300-\032\276\027\320\360\251\305\262m\001\332\366*\256P\374\336q\351\317\003\000\000\240_\007.\000\034x\013\037T\337\236\377=@\365\003\001\000\274R-\002\375\010\271]*9\r\207\354 \000\000\000\366\262=\034i\240;~O\341\3305\200c\357\2421g\322\360\314{i\310\321\261,\001\202\220\000\000\300\026u\326\376\311_;\037-M\306\312|JD\337=3\354\350\237\367\000\365\244\347s\207jv\214\177\000 \026e@^\027\307\376\342\303\261b\020\221\344\\\034\001\312+=\263X\251j\321\263\000\364\241tm\261\327E+\346E\207\341V\367E\371\276#\323\004\t\200$\310N\002\000\020\304\335\027\010\370L\251\321$\303\005\000\000 \205\347\366\317>\020\000\000\346\204\272\231\025\2521\020\302\337QYit>\367\373\225>\237Rf\003t\356\222\315\373\273g\017CtB\017\274/\t\2045\236\362-\000@\313n\232\243n:\354uj-\372\265>\227NH\200N\010\024@\026f|\000\322\010\177%\347\234\331\356Q,\360\211\274\210hv\032\000 \021\253A^\207b?*\n\037J\304\276\274\306k\030~~_K\203b1^;PGv5e\327\213+(\026@\000 55\005p\237\331\031h\366\211\266\335v\332+\007^yz\352\356\355n5a\033\006\020\307\356E\013\000\350\2225\377\220\323\273\332\323\037\320\233(\211V*pM\365\307\025\'s\3051\340v\022=9\t\000\220\315\317\314\177f\376/\265\275\340\026gBO \3061@\034\026w\000:\365\334\226\254\355O\326\326\272\265\347I\351\353\337#\214eK\224I\007\000\220E\266%o\302\032\010o\014\212\344$@.\3432 }QpP\244Q\023\251-i\t\"\000\000k\324\214\'\331=\323\244\303#\373\360\033\001\240\013V:\000R\033\206\351\327\343\263K\333!i\033\236\335k\340%\001\000\000\267R\220f%\362\367\320\357\000\000@L\253\273\035_\364\017\332\003\2555k5\023/\326\332\371p1\t\000\000@o\036\207vy\t+\337\355M>\324\243tO\334\001j3\323\002\r\331\276= $\t@\033\232\250\216\374\337\204x\232H\254\375d\"\000\000\334\257\323\355\004u\004\332\246\311l\370\307x \227@\213\031@%j\203\276X\3316x\351\244x\371]\240E\005>\242q\001[\370lR\300\266\0010\343\247\242Q\374%7\310\201\254*\307]Q\331\262\237\340\307\215Q\334\226\225Ub\0228\362\247\001}\306\247\317\263\256\356H\002\320\200\207\214\246\016\211\005\345Xbc\020\307\344ZK\200\342\347\023|\345\037\312\367X\020o\201\017\330M\337\033\335\200\355\002\200*\202\327\204\300\275\224\345e\350\307\234\252.\321U?\2746#\"\210b\201,\366A\271\350\266\250D\026\000xuam\320\3656\023\000\000B\272pC\000e5\277\305l}t5}~\315G\267\007Y:\261\351L\006\000`\037\305\335\001Y*\177\026\254\3747\260\346s\344\367\364\233?\317\336|\367\353\320x\307\216On\364\333$\255\233n\0104\313\310IN\002\320\271I9@O\204\357rmM\372\022\340IW\314\3201k\226.\366,<\365k\375\025\177.\2306>\035\340\323c_\346\036\347\3338\240\233\302\013\264i\347\000\336\371r\000\200\306\274U3\212\233#\036\017;f\000\216\262\366\002@\233\336\327\350\367\337\273\366\322\230\274\373\331\363-\017\225\023@\n\347g>\"\350~\375\352\276\0017\323\177\000O\243)Q\241\224\211h\247\246\030b\201\364\000\200\313(\312\001\340(\233W\200:\314\257\000\204aQ\333a\347u\312\235/\007\000\332c9\007\2022\275M\244\332\034\213\377~\361\372,^\213\000\000\000\226\330\005\001\000\344\223\352\346\017\000@V\212>\026I\020\000\210\305\332\016\231Mf\200\305/\202,>\t\000\000\000\320\024W2\000\200\350&\367y\200\210\346\206\372\353\343\377\275\374Lvs\031C\022\022\000\000\000 \231\177\267\305\335 \007\000\000\200\256\270\273[N\207}\331\341)\003\000\000\264\302\226*\013\221\006\0023\305\001\300Yi\2777=\214\352\210\264\335\220\272\351\000\000\000t\306=\201\354\356\276\212!\003\201UwOT\000\000+\354k\000\016\262\337\343\213<\000\000\272\246\230\001\000\200\350T\375\200/\005\364\345w\336n1j\377\003\020\243\377\206\213\235b\021\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_height_map_assets/tile-7.textpb
new file mode 100644
index 0000000..83f1fcb
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-7.textpb
@@ -0,0 +1,3 @@
+tile_key: "7"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364\241\311\247\272\014UR\270j\221E;\024\240R\342\227\024\355\271\244\3074\341\326\234)\340S\302\324\2121O\025\"\324\202\236\265*\324\213R\016\265 \247\221\300\247!\035;\324\312)\342\235\232p4\352\rFx\346\254B\341\206\r<\257j\257 \321\022\0265<\361\205N:\325#\2321IM84\204\n\215\224TD\n\211\361U\334Uw\025\003\212\201\305VqU\334T-P\270\252\322\016*\234\253Ud\025Y\305FV\242\"\241a\232\214\245D\351Q\024\250\235k\327\2323\236)\256\016\334\032\213m8\np\024\340)@\245\3058\nP\264\273i\341i\301i\340S\200\247\201O\024\360*E\251\026\244Z\221jE\251\224\016\365\030 \310qV\024\323\301\247\003N\024\340ii\254)\250v\265[S\221\232c.\346\346\254\300\2358\242\362<(\"\263\366\322m\244+LaL5\033TdT,*\027\250\\Ug\250\030Uy\005We\250XT\016*\264\202\252\312*\234\202\240qP\221Q\262\324L\265\031\024\306\025\013\n\211\205{\010\036\265\024\200T;y\245\013N\333K\266\234\005.)\300S\200\247\005\247\001N\002\224\np\024\360)\300S\305<T\202\236)\336b\257SR\t\227\037/&\201\276C\311\300\251\321v\212\224\032p4\341N\024\341N\024\244f\243#\006\254Dr\274\320\300\203V\255\337\212}\340-\030=\2534\214RQ\212c-F\302\243+Q\270\305@\365]\352\026\250XT\014*\007\025\013-@\353U\344\030\315U\220U9\005VqU\330Tei\2053Q\262\032\210\245D\313\216\265\013\212\205\305z\363q\322\240y0phV\rR\252\322\342\212)\300R\343\322\224\nx\247\001N\305-(\247\np\247\np\247f\202r1\232b\214I\216\306\257\244`\n\225i\340\323\201\247\003O\006\234\r8\032x\247\001HW4\014\257J\221X\265O\037\006\255I\363\304@\254\366\217\232aZLSH\250\312\324o\305Wz\201\352\273\212\210\212c\n\205\326\241+Q2Uy\027\025VAU$\030\315R\222\253=@\302\230E5\216:Tg\232\215\270\252\356j\007\250\034W\255<\200\n\243<\302\2515\367\224\371\315\\\203RW\003\232\224\335\016\306\234\267 \367\245\373H\315J\223\206\251D\202\234\034z\324\313\315;\024\264\003N\247R\212p\245\315;\265F\354W\232\204\334\342A\3175\247\004\333\30758\251\001\245\024\361O\035i\302\236;S\3058S\351\030qO\203\275J\006\rJ\200\223\365\250\245]\246\2414\322)\244Tn*\026\250\034T,*\007\025\t\025\033T-Q3TLj\0075RSTe9\252\216*\273\255DV\232\313Q2Tl0*\273\232\200\324l\265\023\255z4\267\003\007\232\313\270\270\311<\325\tK9\342\230\211*\037\224\232\260\262\314\007SR\255\304\242\247K\306\376!Z6wq\271\001\216+ac\211\324\020\334\3242\304\253\321\251\022M\275\352q>;\346\246W\rN\306zR\342\224\np\247\nQN\002\231*dV;\356[\234\023[V\317\200*\3720\"\244\007\024\361O\024\361\3058\032p4\360i\342\236)q\272\236\213\264\324\246\245\214\322N\205\206@\252\244\021I\214\323J\323\031j\026J\201\326\241e\250\035j\026Z\205\226\241u\252\3561P\265W\220\3259Nj\254\225Y\226\242e\250\310\250\330TNqU\2449\250\030TdS\010\250\334q]K\\\356\025\\\202\346\246H\272T\313\030\251\004c\322\235\345\n_!Oj>\317\267\225\342\246I$A\200\306\247Id\'\3469\0251\215\230d\032\201\244\222>\271\305Y\266\273$\326\224r\206\02584\340i\302\236\0058\nv*)\030(\254\231\030I?\313ZP\214\001V\343r\rZV\315H\0168\247\003N\006\234\r8\032z\232\224\032vjd\0250ZF\351\212lNw\n\270pW\212\253\"\342\241\351IM8\250\236\241l\032\211\222\241d\250\035qP\262\324n\234U9V\2538\252\262UI\005Wu\250Yj\026Z\215\226\253\310j\273\344\324%j6\025\031\024\322*\027\256\221b\247\252\000jA\307Jp85 l\323\301\247n\247\251\315<b\246A\307\322\247\004\355\310\374i\031C\212\245*\030\216S\212\236\326\364\202\003\032\325\216`\303\203S\t*EqR\253\n\220\032R@\352qY\367\327\001\020\205\357Tl\327s\3565\265\020\030\251\024s\305YQ\3005 j~iA\247\003N\006\244SO\006\236\234\232\270\215R\001L~\225[y\215\262zU\350\246WN\265\034\247\232\200\3223\201Q\026\315B\3715\001\342\234\016W\232cTL\265\021Z\205\305T\225j\243\255U\221j\254\213U\331j\026Z\205\226\241qU\235j\026Z\205\226\243aQ\221Q\267\002\253\270\256\246\202h\335F\352p|S\303\323\267\323\325\352Uz\231_<T\253&:R\207\301\241\300qT\336\022\255\225\251\340\270)\303U\305\272\351\315J\267$\036\265f;\241\336\246\373j\201\305V\236\367\336\263\036G\270\177j\321\266\214\240\025\240\215\212\231\033\326\254#v5&y\247\003J\r<\032p4\360i\340\324\261\034\346\255G\324U\2208\246\260\252w*q\305$\014Ur;U\304o1zTrD\303\'\025M\362\247\232\001\310\250\334\324M\355L$\212\214\271\357HZ\243cP\265W\220f\253\272\325Y\022\252\310\225Y\322\253\270\250\030TL\265\003\256*\006Z\205\205D\313L\331P\313\201U\034\327M\272\220\265&\3527\321\276\234\037\232xcN\017R+\324\311%J$\251\267d\0029\247+z\323\260\032\232a\006\221\240*F\r&]i\004\222zT\352]\307Jp\267.~cVa\267\013V\200\333\214S\325\252\302\002}\252\324})\344\323\201\247\212p4\340\324\360\302\235RBy\253\221\236EZV\315;\031\250\244\2175Q\334[\003\221\326\241\267\324B\313\216\325\260\223,\313\322\253\\@95A\270\250\330\324D\340\322\036j6\025\013\002)3Q\2675\013\324\017P8\315U\224b\251\311U\330T,*\026\025\023-B\313P\272f\2421\324R\035\242\250\310rj\006\025\321f\232Z\230^\215\364\273\250\335OY=i\373\307jz\275J\257\212\231d\310\305J\222\021R\254\204\234\324\350\371\025&jD\3062FE&\325\'\216\224\341\020\364\251Q@\343\025&=iA\247\356\243uH\2221 f\257\305#*\362*A \'$\322\203\371R\371\200w\247\007\317\265<\020\335\351\341G\367\215/\314:05$nC|\325z3\337\265ZF\251\224\322\221Y\372\224d\247J\310H\210`En[\202\261\256MYl2\237Z\314\230`\232\254MF\306\224\036)\254*\027\034\032\216\230\325\023{\324\016*\273\325Yj\243\216\265\003\212\205\224\324ej2\265\023\255@\303\025\014\214*\224\315Y\357\'\315Q\264\265\320\026\246\226\250\313P\032\215\364\340\364\340\324\360\324\365z\225Z\245G\305L\255S#\35550q\236*P\334T\261\313\216\243\212\220\225\352*h\361\267=\350i\001n)w\322\356\24058\032r6\r]\023\226@\270\351J\244\324\241\370\245\r\315;u85H\256j@\364\354\325\313yr\274\232\275\033\202*ej\225Z\251j\023\2026\016MS\215G\031\255 \000E\307J3\317\025R\344s\232\244z\324mB\232SP\310j#\322\243j\205\315D\306\242|Ui\024UWJ\256\313Le\250\\b\253\261\305W\222LU)\256\000\351T\244\270&\252\311)j\254\325\023WB\315L-L-I\272\215\324\007\247\207\247\206\247\206\251U\352D|\032\235\037\336\247V\315J\215\371\324\252\365 j\230>\341\322\236\257\371\324\204\250\307\257zqe*1\326\223u8\032pj\221\r^\205\224!\365\251RA\214b\220\236y\2434\240\323\305H\264\360i\340\324\3219C\317J\277\034\230\344T\253p\007=\252\031\365 \200\205\252P\271\231\2131\315]\333\307\025f\026\334\230=\250\'\006\240\270?-S&\243jh84\273\252\'94\302x\250\232\241~*\0065\023\032\211\252\273\340\346\240b\001\250]\200\252\322\275Q\232`\277Z\317\226Fj\254\365\003\212\205\205B\302\241j\333-L-L-M\335F\352]\324\340\325\"\265<5H\034c\336\236\216*Tz\260\262qS\307&\322\rK\346\2069\003\025 \223 T\310\343\036\364\360\370\346\237\36374\241\251\301\251\301\251\352j@jdsVT\232\221NMHq\332\200*@)\343\232P)\375*Tn0E=da\307QJ]\210;\016\rUh\345rwQ\004\255\013\355qZ\221I\270U\210\376\\\232Rj)\233(j\236)\255Q\221\315!\351Q=B\315\212\214\277\255F\346\252\274\233MFe\035\352\026\225}j\027\220\016\365VI@\351U$\230\325Ief\252\257\317Z\205\305@\342\240qP\260\250\036\241j\325-L-L-M-@jp4\006\251U\251\341\251\301\251\352\3252\265J\257S\253\344T\350\330\025*H\000 \363OW\251\225\263O\363\010\030\007\212\221Yvd\236i\003\023\323\245=^\244V\251T\324\311\326\256G\265\207&\226\236\rJ\244S\201\251\001\247\216\264\341\315=jE\247m\311\310\353OV*p\303\"\244\222\3329Wp\024\220\257\226p{U\235\324\205\252\263\271v\300\351JS\002\242aQ54\216*7\351U\332\241`s\315B\344\363\203U_\336\240r\rB@\'\236*\tT\212\254\365]\352\007\252\357P\260\250\034T\017P=B\325\003\n\320-L-M-L-@4\340\324\340i\352j@iwS\203T\252\365\"\265X\215\252\300$u\251\021\206EX\334:c\232Uz\2205.MM\023\200\016ic \223\272\245FS\327\212\224\020:\034\324\310julT\310sR\212z\340S\367\np>\224\360\364\360\324\365j\2205L\2074\346 \nXe\031\332MJ\024~4\355\330\246\034\277\002\200\241hcU\330\344\323\010\250\311\250\230\324-\315D\375*\273Uy\006j\263\214T\017\232\201\311\250O\0078\252\362rs\212\205\233\003\201U\236\241z\201\333\214Uw\250\032\241j\205\252\321jijaji4\252i\304\322\253T\252\324\355\324n\247\007\251\025\252Uj\231\032\254,\234T\210\334\324\301\360\335jM\340\236*Ej\2206i\301\260jEl\234\232x8\251\221\252ej\235\033\246j\326\365\000c\255;u(l\323\201\251\003S\203\323\203\324\201\351\352\3652\2759\233\"\230\215\207\253Fb0iL\231\031\024\365\223\tM\017\232k\034\323\010\342\243cQ6OJ\214\241=\351\214\204Uy3U\3335\003\325w\250^\240aP=@\340\212\254\365]\352\0075\003\232\201\315@\365\013T\017S\223L-M&\232Z\200\324\355\364\241\271\247\207\247\007\247\206\245\006\244V\305J\255R\243\325\200\371\002\246\215\307B)\340\374\330\0257\335\305J\215\305H\247&\244\347\322\234\032\244\014*U\315H\255\216\265:=L\036\244W\355R\003N\337N\337J\032\236\036\234$\251U\352tz\227vEF\371\007\"\246\211\303\'5*\256\345 \036\224\210\340\214\032yB9SI\311\353Mj\211\215D\336\324\336E1\213\n\205\333\332\253\276*&\013\216z\325f\343<Uw\250[\245B\302\241\224n\252\262%Uu\305WqU\334T/P5B\325\013\323\213S\t\246\223I\2323I\232pj]\364\340\364\360\365\"\265I\232z\265N\246\246\rS\241\356:\324\321\311\216\243\232\220\023\336\244V\251\003T\313\'@zT\203\004R\240\311\353S\253\355\340S\367\344\212\2205J\257\212\2208\251\026J\223viwR\207\247\007\247)\251\225\252Uj\234=;9\246\357h\217\003\"\246I\207U\342\225\324\246\037\261\251\221\362:\322\223\336\242f5\033\034\324g\"\232}\350b\010\252\354*&Z\201\205@\342\253\270\250XT\rP=W\220Ui\005V\220Ug\025\003\212\205\205@\342\241aL&\232M4\232L\321\272\2234\233\250\335NV\251CT\212\325(jz\265N\255S+\342\245V\251\327\234{\324\341\370\332{T\212}jQ\202x8\247\364\034\034\323\324\364\317J\221\037i\310\251\003\356l\232~\374\267\024\360\334\324\201\252da\216M<5<585;u\000\324\212\325*5J\257R\253\324\252\365/\017\326\223\312\301\371N)e\220\354\t\327\232\2263\201R\026\342\230G\031\246\026\035\206h\013\270g\275B\352W\255G\273\025\033\021Q7\025\023T\017U\336\240z\201\352\026\250\034Ui\005VqU\234T\014\265\003\212\205\205B\302\253\023M&\232M4\265&\3523HM\000\323\201\251\024\324\252\325\"\265H\032\244V\305L\255S\243T\350\370\251\223,x\251\225\261\326\244\rR\003\351R\253\345pi\350\t\351O\r\212pjr\265J\032\236\036\244W\251\003S\303S\303Q\272\236\032\236\032\246V\251\025\371\251\203T\361\276\0075(j\205\2372\014\366\251\321\352V8\034SKn\030\246\001\212_\247\024\016xj\2574E\016GJ\256y\250\330\324,j\007j\201\315WcP=D\331\250Z\241u\252\356\265^E\252\356\270\025]\305@\302\240qT\311\246\223L&\233\232J\\\322\023E(4\360\325\"\265J\246\244SR\003R\251\251\321\252tl\324\350\307\265L\030\2202jEj\225H\357Rdg\212\221\033\007\"\244$\261\317Jr\343<\363K\236x\247\003R+T\212\325 j\2205;u.\352pjxj\221Z\245V\251\321\252Uo\230\n\262\274\324\027\n\335S\255@\223\310\207\346SV\222\3600\301\340\324\261\271bI\351R\003\232:Q\327\353J\300H\244\036\265I\320\2515\013\212\201\205W\221j\006Z\205\326\240e5\013q\326\241qQ\267J\256\342\253\275Vz\256\302\241u\252\356+<\232i5\0314\334\321\232Pi\017Zu\024\340i\352jE5 5*\232\225ML\206\247F\305Y\211\271\346\246SOB;\323\267b\254\"\202\231\3174\261\261\006\254;p1K\021\014piH\305\001\251\342\244V\247\206\251CS\203S\303R\203O\006\244\rR+T\310\325:\034\221V\221\252P\271\243\310\004\321\366A\235\330\253\n\243n\010\305A(1\237Z\204\315\203\2021NI\206jRy\014\275)\222\000\334\325WZ\201\220T\016\225\013F{T\016\225\t\030\355P\270\007\255V\221qP0\250\331sP:qU\335*\273\255VqU%\006\262w\323K\323KSwQ\272\234\032\235\232\001\247f\224S\305<\034S\301\251\024\324\312jd5:\032\231Z\247V\251\024\323\301\311\251s\267\200sR!\365\342\237\270\376\025&\010\000\323\321\371\245lg\345\247\002@\346\236\017\24585H\036\236\036\234\032\236\255R\003O\006\244V\251\025\252\3025Z\211\363V\220\324\353R\nx\024\025\r\324T\022[+d\342\263\246\267x\330\225\351K\024\344|\257\305M\272\242j\201\205B\342\242\'\025]\352\273\324\016*\027\\\212\256\302\243\"\242qU\244\025ZN\365Y\305D#\311\311\2546U\r\301\310\246\230\201\357BC\273\275G2\252q\236j\024\345\261R\310\2331\212h4\274\216\264\3455 4\341N\3158\032\221ML\246\246SS\241\251\224\324\313\310\310\251\024\324\200\323\324\324\310A\353S#\214`\324\252\343\'#\212L\363\300\342\227w9\2517\226\306iA\247\251\247S\207\024\345lS\303T\201\352Ezxj\221Z\247\215\352\3227q\326\256D\371\025eMJ\246\244\006\226\203\315D\350\017Z\245s\007\004\257Z\253\034\204pz\212y9\250\332\241qQ0\250\034T\016*\006\034T\r\301\250Xu\250\033\203P\271\252\317U\334f\241)\232F\\q\\\256\r4\222)\003\262\3645\033e\2174\344m\224\346\224\2767S\323n\340j\304\221\243.\340qQ\004\\q\326\215\244S\226\235J\rH\246\246CR\255N\206\246\\\232\263\033\014R\347\232\2205=MJ\246\246SN\r\203R\371\271\024\356\n\217Z\010\333@jxjpz~\372pjx4\340jEj\221Z\244V\251\221\252\324OV\342c\273\025q\rL\246\244\006\236\r\031\246\232\212^\225\224\343\023\034S\373S\032\242j\211\205B\342\241qQ\025\315W\2250j\263\212\256\375*\007\346\240aQ\025\246\021\212\255!\346\271\242)\244S\010\244\333M+I\266\224\np\334x\315=r\rNHd\344\363LZ~(\035i\353R\245L\2650\251TT\311\301\342\245\335\223O\006\236\rJ\206\246SO\006\234\r<7\024\273\210\024\375\300\217zvW\034u\247.\336\364\270\317\335\244\r\203R+\324\200\323\201\251T\324\212je5b6\301\253\221\276q\216\265r6\316*u5\"\232x4\354\322\023PJx5\234>g&\236j3Q5F\302\242lTOP\236\rG\"\344UI\027\232\252\342\253\313\305V2`\323K\203U\335\362MW\222\271\242qHZ\232M&h4\001KN\002\237\212Jr\324\202\223\2758T\253S\251\251\243\035\315H*d4\360zT\200\323\301\251T\324\212\325 4\340\324\355\324\271\247\003N\006\237\237JP\304\016)\353\2029\353I\312\232z\275H\247\232\224\032\231\rL\246\245V\253\220\2660j\344g\025aZ\244V\251\003R\356\244-PN~CU\020b\221\2150\232\215\252&5\023TMQ\232i\344Uy\026\252H\265B\343\200k8\276Z\230\362\021ML\221\232d\225\3160\315DA\024\334\321E:\227\212QO\035)*@0\0058t\245\305-=*d\367\253\010r8\251\000\251\024\323\305J*E\305;#\265=MJ\r85874\270=\216i\300\221\326\234\0334\340y\247n\315(<\324\231\365\353H\016\rJ\017\245J\247\212\221\032\247SS+U\270\030\021W\021\252uj\225Z\236\032\227u\005\252\t\233<T$\342\230MFM0\232\215\252&\250\310\250\310\250\311\305B\346\253Hk>\353\2258\254\365\213\004\223PL2\334S\320aj\031k\234ja\250\310\301\246\232ZQN\353J\005<t\244\247\216i\353N\245\3059je5b/Z\2274\240\324\212jE5 4\340i\340\324\200\323\272\032:\322\362:S\203\032xl\323\201\247\003N\006\235\270\223N\316i\352qR\241\'\245H\207&\247SR\253U\250[\006\257#T\352\325*\265<r(-Lg\307\326\242-\334\323\t\246\023L&\230i\206\243ja\250\333\245@\365VW\305Vy*\214\244\263{T\017\307J\252\303-O\306\005V\224\327:\324\303L4\303IN\006\236\r8S\207JJr\324\203\212x\245\245\025*\325\210\317\002\236)\342\236\016\rH\r85<\032\220\032x8\247f\224\032v\352Pi\340\322\346\236\r8\036)A\247\203O\006\236\255R\247&\247\004v\251\024\325\3301\216z\325\244j\260\255R)\251C`\363Li\007\'\265G\273\'&\232[&\232M4\232a\246\323M0\323\010\250\330UyN\005e\\\312rqU\203\356\024\326\305V~j2\270\244n\225Rc\326\271\363Q\232a\246\232i\245\024\341O\006\237\221\212JU\342\245\316qNZu8\nz\365\251\326\244\006\236)\342\234\r8qRn\315<\032xjp4\354\322\346\234\r<\032\\\323\324\323\263J\r<\032vi\353R\243b\246F\343\025<c$\003V\224c\245N\222c\212\264\215\305J\207\232s?5\031$\375(\315%\024\204SH\246\232n3HV\2435\004\215\212\316\270\230\034\200k6f\004\324C\332\220\212aZ\211\352&\351T\346\357XMQ\032\214\232CGZAN\035i\302\226\224\032vx\251\242\0314\3420i\302\236\0059x5*\324\253O\024\341N\025 \247\np4\340i\340\323\301\245\245\025 \245\247\016)\331\245\31585(jxjz\232\231\032\254\304\325r6\350z\325\205\000\266EXSR+S\272\321\212P)qF)\n\323J\323N\0050\232\215\232\240\222P\240\344\326]\335\340\350\reKpOJ\215r\375jP\270\244j\215\215WsQ=T\232\260\232\243j\210\322Rt4\235)\342\234)M(\247\212\222#\206\253.\001\031\024\300*E\024\270\251\005H\225 \245\035i\342\244\024\354R\323\205<S\326\235@\251\001\247\nPih\245\006\224\032x5\"\232\231MXF\253P\276:\325\224|c5eZ\244\006\244\rN\006\234)\331\244\2445\031\2463\001U\345\235W\275g\334_\355\007\006\262\246\277f\316\rSfy\r\002/Z\225W\024\343\322\242cQ3T\016j&5Z^\225\204\325\033Tt\332i\344QJ*U\240\214\032QO\024\364\034\212\262y\300\245e\332i\313N\002\234\005H\240\212\224\014\323\261\306)G\025 \247\203E(4\340jU4\354\321\212u<\032v)(\024\352\0058\032z\232\235\032\247CS\251\351VCg\034\346\255\306\334\n\2305H\2475*\323\300\247b\230N*&\220Uy.@\3175Rk\265\037\305Y\267\027\231\350k9\313\312}\251V\337\035jA\020\024\245E0\323\030\324\016\325\0135D\306\231\236*\274\335+\021\205D\325\031\246\032m(\245\024\354\372R\203\332\235\214S\305H\275j\310q\263\035\350\352i\340T\202\224\n\221jT\031\2470\244\357O\024\340i\324\231\305(<\324\212\324\360j@iisK\232\013\021N\355\223A\342\200iA\251\024\324\252jt5:\266*\302\2779\035j\304.s\212\266\255R\251\251\224\324\242\231$\252\200\344\342\262\256\265UN\024\325\003~\362\0363P\310\362\037Z\200\243\267SJ \035\352A\030\035\250*\0054\212\215\215@\315\212\211\232\240f\250\031\271\2461\250\313qPL\334\032\310j\205\205Fi\246\232E%\024\242\234\016jE\367\251\027\322\245\013\307Zp\253p\306\031\t\364\244\3075 \031\247\005\247\201S(\305!\240S\251\302\235Hi)\342\236)\352i\340\322\346\224\032\0174\354\2221J)wQ\332\234\2652\032\260\225\"\236jp\340~U,r\200\3035z7\315XV\247\371\252\275N*95\024@y\254{\273\366\231\260\235*\232\304\\\345\3715ec\000T\235\260E!@G\035j\"0i\013b\230Z\242f\250\235\252\007j\201\332\240v\250\213S\031\252&n*\274\255Y\306\243aQ\232\214\322\036\224\332(\035i\342\236\265*\212\231E?mX\200\237\272;\323\366\025<\324\200S\200\251\024S\263IE8S\305-6\227\024\242\236:\323\205<\032\\\322\206\245\006\235\232Z^\264\202\236\t\251\227\246jdj\2234\370\316[\025$\352c\034u\244\217P\300\303pEL50\007\025^[\311%\345sP\215\357\367\215J\251\212\225x\247\206\240\2657v)\215%F\315\273\247Z\205\233\025\031z\215\232\241sP9\250\030\324,\324\315\325\023\265W\220\3253L5\023S\r!\246\232J)\342\236\2652\324\350*]\265f\331rH\003\232V\316\356i\352)\370\247\201\232\\`\321@\024\016)\353K\364\245\307\024\224\242\234)\302\226\224\032Zu.i\374\020(\317\006\200i\342\236\246\245\rR\006\247+\340\361VZA*\017QU\236\020\306\225b\003\265I\214\014P8\245\335F\372<\312<\312B\324\306j\210\276*3\'\2551\210\307\025\0215\023\034\324Lj\0075\003\032a5\023\232\255#T\rQ\032\214\323M4\323M7\275-<S\326\247J\260\202\245\002\254\333\022\255\305:C\227\310\247\250\247\342\227\265\004~\224\240\0222(#\201K\214\nQNQ\326\227\024\230\243\024\264\340i\324S\251A\245\245\351K\316y\242\234\032\236\r<\032xjxj\261\033|\206\231\346sN\337\232pl\365\246\027\246\357\244/I\276\215\364o\246\027\246\026\246\023Q\261\246\357\3051\271\372T.\010\252\356j\0265\0315\013\032\256\346\243j\211\2523M4\323M4\332Zx\251\026\246J\263\035N\242\254\333\256\343\216\346\245\222\023\030\311\246-IJ(\31794\240q\301\243\2674\270\247\001J(\245\024\237J\\R\201\232u(\240zR\347\024\341\322\227\255-\024S\201\247\006\247n\245\rOY\010\342\215\374\322\211)\376g\035i\205\350\337H^\223}&\3727\322n\246\226\246\026\246\226\246\023LcL-\236*\031\024\212\200\367\250\\\324,j\007\250\330\324f\2434\303\326\232M#SE8S\205H\265:\n\263\035N\2654Rl>\225l\376\372\"T\222E@\243\006\245\024\224R\212p\346\224S\251\324b\2121E(\245\242\234(\306iz\032ZQK\212(\245\006\215\324n\243u.\357J7R\357\240\271\305\001\350/M\335F\372]\324\233\251\013S\013SKSKSKTmM2z\363PI\355P1\250^\240z\215\215FM0\323\r4\323Z\212QO\025\"\324\311V#\253)O#5j\331\202\251\365\246\377\000\025?<SM&i\300\323\251\300\376\024\341O\300\365\245*G=i:\322\322\342\223\030\245\0034\001\353KI\322\226\234\264\352BqJ\r!\244\315\031\030\246\226\"\215\324n\243u.\3527Rn\243u\033\250\335I\272\227u4\2654\2657u\031\246\223LcQ1\246\026\343\006\241\221p2:T\rP\275D\306\243&\243&\232i)\244\346\212QR-H\005J\265a*\302\032\222\254[\270V\344f\234\347,H\242\220\322QN\006\234\r8\034T\213\3158t\243\003\034R\212\\Q\266\212)GJCE(4\374\322\023M\315\004\346\214R\036)\244\322d\322f\223u;<P\r\004\322n\243u&\3527Q\272\215\324\322i\244\323sHM4\232a5\023S7v=*\031\007\247J\201\252\273\032\215\2150\323I\244&\233\232\005<T\212jAR\255N\225a*A\322\246\204e\205J\343\r\315&i\t\246\320\r;4\340i\300\324\200\342\237\232\\\343\2458\014\232p\030\245\002\220\212L\322\322\036\264\224R\346\202sM&\214\322\203E!\024\302i\204\322\203\232\\\361\212L\321\2323M4\322h\335F\3527Rn\244&\232M!4\322i\204\323\t\250\332\243\'\326\242qU\030\324f\230M4\232i4\231\245\245\025*\324\200\324\250j\302\032\235jQRF\373H\305Lr\347\216i;\322\036\224\323I\232\\\323\201\247\251\365\247\203R\016\224\361\357R\014R\321\320\321\326\223\034\321Hi\010\315\035)3Fi\244\321\273\336\233\236iwzP^\230\315L\245\315\031\2434\240\320M74\322i\244\321\272\2234n\243u4\232nh\3154\323\032\243j\215\2522}j\2214\302j3M4\322h\2434\361OSR\003R\251\253\010j\302\032\224S\207Z\274\233V\023\317&\253\347\232Ri\246\231K\232p4\360i\352x\367\251\007J\220\032\220S\261K\214\321\212)1Hi;SM%!4\322i\245\2517Rf\2234\322h\315\031\367\244\335N\316G\024\252q\326\220\232ajBi\244\322\026\244\315\033\250\315\004\323I\244\315\031\246\236j6\250\215F\325@\232i4\302i\204\322f\214\322\323\201\247\003R)\251T\324\350j\312\032\235iI\305[\265\304\200\2065\034\213\261\310\240\021McL4\200\323\201\251\024\323\324\324\240\361O\006\244SO\006\235J\r.8\246\237jCHi\264\323L=)\244\323OZ\030\00085\036h\315!4\231\2434f\2246)X\372SwqL&\2234\322\324\233\2513Fh\315\031\244&\233\2327PM0\324mQ\232\316&\230M4\232a4\231\245\240S\307Zp\251\005H\246\247CV\020\325\2054\254x\251l\345\304\240\032\275=\271\220\3461\237\245Tu1\360\334T{\263M&\233\232p5 5\"\232\221MH\246\244\006\236\r8\032\\\323\267qIHi\246\222\232i\206\232i\215L&\2234\231\244\2434Rf\220\2327SI\244&\220\232i4\334\321\272\214\322f\2274\231\244&\2234f\220\323\rF\325\226M0\232i4\322i3KN\024\361N\024\361R-J\246\247F\253(\324\342j5b\216\010\255\233=McC\270U\033\253\257>B\325\016\352B\324\231\247\203R\003O\006\244SR\003R\003R\003K\232\\\322\346\2274f\220\323M4\323I\346\230M0\323\r4\323sFh\242\220\360i\271\2434\231\246\223I\232i4\334\321\2323KFi3M&\2234f\214\346\232MF\325\222Z\230Z\230Z\223u\031\245\006\236\rH)\300\323\205H*E5*\265L\257O\337HM cN\006\235\272\220\265\001\252E5 5\"\232\220\032\221MH\r<\032p4\264\240\322\321Fi\246\233Mja\246\232a\246\023L&\2234\240\322\346\220\236)\204\323sM-Fi\t\244\315!8\244\315\031\245\315\024\204\323I\244&\2234f\202i\215X\244\323\013SsIFi\300\323\324\324\200\323\301\247)\247\203O\006\244\rO\rO\017N\rK\232]\324n\2434\240\324\212jE5*\232\220\032x5\"\232x4\340i\300\323\250\315\024f\214\323MFi\244\323s\351L&\230\306\230i\271\2434\240\323\331~\\\212\210\234S\017\037Ji4\231=\251\271\2434QE&h\315\004\323I\2444\231\244\315\033\275\351\t\254&ja4\334\321\232\\\322\203R)\251\001\247\203N\006\236\r8\032x4\340i\301\251\341\251wS\203R\346\226\234\r<S\301\251T\324\212j@i\300\323\301\247\203N\006\236\r.i\t\244\315\031\244&\230M0\232a84\322\336\264\322i\215Q\232\006M(4\340\347\030\244q\305DM0\232M\330\246\226\240\032v}(\315\035h\351Hi3IHM0\322f\2234\026\254\026ni\244\322f\214\321\232p4\360jE4\361\322\236\r8\032p4\360i\300\323\201\247\003N\006\224\032p4\340i\340\323\201\247\203R)\251\024\324\200\323\301\247\203N\3158\032p4\354\321I\2323HM0\232\214\232a4\322i\244\323I\250\311\244\007\035i\336\342\223uI\346\002\270\"\241c\315DM4\232@\330\315(\351\305(4f\2274\271\244&\222\220\323I\246\223M4\334\322f\260Kd\322\023I\272\214\322\346\224\032z\232\221O5(4\340i\300\322\203N\006\234\032\234\032\234\r8\032vi\300\323\201\251\001\247\003O\006\244\006\236\r<\032\22058\032x4\360i\300\323\263Fi\t\244&\214\323\030\324li\204\323\t\246\223M&\230M4\236jEn9\2466G=\251\003qL-M&\230M&i\331\030\343\255.sFOz(\315\033\250\316h\246\032i8\244\246\236\264\206\271\342\334SKR\203FiA\245\006\236\rJ\206\244\006\234\r;4\340iwR\206\247\006\247\006\247\206\247\003N\006\234\247\232\220\032p5 4\360i\340\323\301\247\203O\006\236\246\234\r8\032v\352\\\321\232ni3HNj6\246\023L&\230M4\232i4\302iA\342\244\335\270TG\214\342\230M4\232ni\t\240\032x<\361O\335\232C\3056\2234f\214\322\023M\'4\204\322f\232k\233-I\272\215\324\240\322\346\234\r9NML\r<\032x4\240\322\346\215\324\240\323\203S\303S\203S\203S\301\251\024\323\301\247\203O\006\236\r<\032x4\360\324\360i\340\323\201\247f\234\r\031\2434\204\322f\220\232i4\303Q\232a4\322i\204\322f\214\322\206\301\346\2069\025\0214\334\342\233HzQ\236)CS\325\251\375E34\323II\2323Fi)\264\231\256_4f\224\032\\\323\201\247f\236\265 4\360i\300\323\201\245\315&i\300\322\203O\006\234\032\234\032\236\016jPi\340\323\301\247\203N\006\236\032\236\r<5=MH\032\22458\032vh\315.\352Bi3\357I\232L\323I\246\032\214\323\r4\323s\326\2234\023K\234\214S\032\230M%%&h\3158\032z\2659\271\346\230i\206\220\2323Fi\r!4\225\312f\2274\240\323\201\247\003N\006\244\006\234\r8\032x4\240\322\346\214\373\322\203N\rJ\032\234\032\244SR!\251\001\247\206\247\206\247\003N\rO\rOV\247\206\251\001\247\203J\032\234\032\235\2323J\032\202i3Fi3HM0\363L&\230i\206\233\330\323s\315-\004\323I\374i\207\332\232M&h\2434\003N\rO\rIM4\323\326\212(\244=i\246\271,\322\203N\006\234\r8\034\323\301\247\003N\006\234\r<\032]\324n\2434\271\245\006\234\032\234\rH\rL\r<\032p4\360\324\340\324\340\324\360\324\360\325 4\360i\341\251\331\245\rN\rK\232\001\245\315&h\315&i\t\247.0sM+\270dT\0140y\246\023M\355M\'\2323A4\323M&\233I\301\240\373RQ@4\340\324\340h\246\236i\017ZJPh4\206\270\372p\247\003N\006\234\r8\032Pi\331\245\006\235\232\\\322\356\240585.iA\247\203R\241\251\001\247\006\247\206\247\006\247\206\247\003O\rO\rR+S\301\247\206\247\006\245\31585.\352\\\321\2323Fh\315\006\223v(\017\214\212B\233\206sP\260\3052\230zSM\031\240\234\323M74\032LdqI\232J)sN\rK\234\321M4QE\025\306\212p4\340i\300\323\201\245\315(4\271\247\003K\272\227u.\352\003S\267R\203N\006\236\rJ\247\024\360i\301\251\301\251\340\323\203S\301\247\206\247\203O\006\244\rO\rN\rK\232pjPisF\352\\\373\322f\227>\364f\212m\001\261Ms\232\214\323\r0\232nisHM6\212L\322Q\232L\321\232\\\322\203KE\024\235\351h\2560\032\\\323\201\247f\224\032viA\245\315(4\003K\232]\324\003N\rN\rN\006\234\246\245\rO\rN\335N\rN\rR\003O\006\234\r<\032xj\220585<5(4\340iCR\356\245\315.i3K\2323Fi\t\244&\232M4\232\214\323M4\232L\321\232)\t\240s\305%4\234\321E\031\2434\340h\315.i(\242\270\300iA\247\003N\006\2274\240\322\346\214\322\356\245\315\033\250\315(jv\352Pi\341\251\340\324\201\251\301\251\300\323\303S\203S\303T\201\251\301\251\340\323\301\251\003S\201\247\003N\006\235\232\\\323\201\245\315\031\2434f\227>\364g\336\2234\231\244\3154\232a\246\323M!\244\315.i\r\024\037j@3IHi)s@4\354\322\321E\025\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\244IDATx^\355\335\211r\343\250\026\000\320\224\373\377?9\256\031\333\261\343\310Z@\002\304rN\275z5-K\010.\227EJ\267\363\365\325\266\353\364@\264\313\364\000\000\334\214\263@\034_L\001\240I{\227\300q6\tc\333\233\037\000\000\264\304\356\276\021:\352\240\016\236o.\222\000\000\000\006\322\301C\014\201<\354\301AM\017\242\313y\023\376Y\367\345\227>\030\234\004\340N\"\000E5\275u\206\303\256\327\nV\336\3470<\241&\'\334\362t\225\266\271T\265\336\356S\352\226T\306\302\017\000\3000\376<\365D>\377\337O\216\272\002\242\310\256l\016=\366\036\272\230:\351Tj\362\234\374\345\345\240\254\376\000\371\305\316\265?\347/^\266\370\301\016\347n\000R\266\244s\347v\024\345\\7G\205\\\200\256\254\217\371\365O\2419\326\260\235z\t\\/\355\330i\360\346\177\215\033\201\200w\357K\217\000+\227\354\267p\257_\233\'\354\223\251\330.=bu\375\372\367\236\001\007\003\370H\262\333\377gI\2537\007+\372!w}a0\251\207\350_\006lv\331B\23473\340E\252\001,Z\233\"#\267\0001\247Oo\033s-\211=\202\177\371\372\236\034\237\232v\322\264\023\243\035. \334\322\033\260\321M\373t\206\310\301\235\241\000\260\311TIE\244\343\246\303!\nx\226\370\343\360\r#\224\274W\007\256\361\275\031 C\221\201.2 Z\300\357\364l%\246\263-\271\037\014h$\351\265\2228\225\0216\240\214\214Kc\306\242\367\252\260J\220G\360Fbrb\372A\022\\\223dnw\374mG\371\3737\345\021\250G\220\362\206\352\377\322\237?\244\377\351\235\340d\313[\261:\004\007\2431s}7w\214\332\365\232\241\0041h\233\224\270\333\022\027G\313\316X\021\316\270\'U\221\002\305]oQ\177\306\375\300\032p+\342~\371L\031\227\231c\207L\0373S\227\237Yc\325\335V\313\317b#\177\267\306I*\t\026t\302\210:*\305\274\231\242\014\330 \3152\022\\Ng9/\357}\340\357\216\177\231\331c\366m\305\373\037\312T\243c\367\010\177<\324O\377\274\350-\376i\272\"M)l\272D\364\362\231jI\210&\202u\323LE\003\325\222\000\024\360\231\274\237G\252\325PU[!\244e\375|?\364\364\360\026\335D\016\323o\004\223g\235\233}\340g\034\021#<\342\324t\256\263wm\345a\272\025^\361\035\324S6N\022\240\227|\310\334E\231\213\317\245\321j\327b\272a>\333\376\356\334\177%pH\225\203\257\262\251\255\177\227\023b\036z\303\327y\211S5\364\376\275I\034\306Wy\251\313}\312U\356K\366\033\264`\324\321\000\037\014\006\306r0\343\017^\276\310\322\014\220T\360\264\032|bn\327\327\032SM\225\032\262\024\263\245\343i|\374[\2034\362\224\032\346v\357{\036\236Y\211\302\006j\352\036\231\262\234\212\215\326\345\271\036\3578\323%\274cC\317\243k\227\361\246>\350\313\357\243\364\333\221m5\014\374\245:\334\366\340A\215\240\037:\374\220\370\360-\215\276\342\342\253\016\300\274\364S{\372\022\311\350\325]\326V\302\370\301G_\214\374\341\214\333\345\346.\346\311\214<\262\3155:\214td\023c\371;1\313\377%\003E\246\305\246^\037\265\316\266\315\030\312F\002\314~|\213\374\354\007\231=\357\371\375\347\350\303\341l8\243Ag\t\014VHH\002\213\312b.\0178EH\252P\205T]u\346\310\'\241\310\216L\225?T\241\325\356\214\314Z\226\034H\200\003\227&TG-h\200Ic\215\350\214mi\"\275\347\305\334\267\020\317\035\203\362n\251\273\224\276\344UM\3347+b\276\n\323o\230\372m\031\214\252\245Q]w]\227\227\320\345O\316WwL\233Rs7/\370\255\262\177\250RLOC\256\247\266\000\000df\303\315\017\273h\316\"\367r\272O\362!!\356\352\347k=\265\005\010`\320\307\020\255A,\375`E\002\014h)\031*\325Vm\213\022\0326t\230\"\317&u\330\264\014\032\233\355C\255\356\\\356M\356\263\335\303\3534\237\353\266:\332h\307\336\216\334{\035\365\212\232H%\000\014-j\276(\305\274\224\334RH\257\313\0371\222*\277\267\257\253\277\273\000\000\241r\274\030]+\362\343\263\217\003\347\261\027H$GR\001\355iy*\270\327\335\252\020\353\362w\001h5\200\337\267\212\377\274 \270\264\333\214\363\265\033\271[\036\267[\373\032\264<\375\1775_}\356\222\366b\322\302\022\253\264n}M\241\247\006y3\224\247\326\256{uDw3\t\250\333\301\016\\\272|.;\347\216\245\260T\207x\351JjG\3126\317<k/\024\237+\027\206\260\020\323\363}V\354\363H]\374| \277\231\020\317\034\242\177\277\263A\235\tP\373l\005\344V\347\334D1\022\240\2544\361n\357/P?*\334h\265\017\230\351\357\303e>\335\313NV\0327\257p\212+\000\220X\242\355\305\314\366\222`\211:\201rtYaf\230\206u\327y\377\372k\0221.\022\340T\242\017\230\010`xf\001\350\310\216\367k\346\200\236D\'@\364\005\324\315x\236\032,\"\2035\027\210`~\200\\\306\332O\233K\022\020\304\303\204\020R0\222\000\000\310\256\350\246\263\350\315\200\342\306z\013\013\345\031c\000D\032\355\021l\264\366\206\312\025\227\327\326$\327\r\240\022R\034>\030\026\000\000\205\005\377x \370\304E\037\277O\341{\362g\212y\337w?\376\273\334N|\232\006P\251r\203\002\000\200\363\331\375u\306\223\347\311&\035\240?\006\327P\002X\014``\r\315U\000\225\351b\013\265s\031\350\242\355\275\320\031\033v&\371\207{93\205=;@G0\034IO;d+\274\271\030\021\207<\267\2033\333B\250A@j\0161\t<\246\272\350\246\006\204\217&\374\364d\262\016\215N%\316\225\252\347u|\363R\245B_\306J\354\261Z\233\226\3301\022\353EeL@{\255\246\362xa]\r\307\233\311y\327\351\001\232\264\222\360\037_\361\360i\373\014*\267\222\000\237\364\367P\002\272;\340\024\240I\177Fw\324JA\027\226\246w\271\360\264\024\241\023\235\3319\025\206\343\200\276Z3k%Yv\265~\327E\355\352\247\271+\211\020\255\237\250\214c\265\317V?\234J\231I-k>\016\3157\340d\342\007\264+j\335\357\226y\234\036\254\216\346\225\017\223\344\377\277\351\201v$i\177Y\025V\371Y\245\n\253\306\'\3354\234\253N\007\312ha\262Y\331\025\267\255\205\340\367C\264\233\367\234\t\032~\216\335\257\333i0?\241\013\362\372\327\006\325O\225\325W\020\000\000v\261\323]t\211y\254\21397\271\2147\317V\364\275\340l\245\0374\251\327V5\347>7\260\2327\327\255S!\347\320\255\327(7\334G\'\003\000\200B<\200\320\213\2507nP\234\207\274A\351x\300L02}?\274\200\257L\337\"\213\032\027\233\002:\034:b@\303\270b7\000\r1\265\225\2623\211v^\006\320\201z\226\250\357\235\223\361l\013f\017R\267\235\t\300nUE\274\252\3120(+\307\340$\000@\303\354%\001\000\000F\343u\036\264\307\270\005\362\363\256x0\226\226f\350*r(0\347\027\270\005\014\342{z\240vV\256\204\022|Q\004\0041p\273w\321\3074A\242\002\0000\252\337w@v\3050,/\203i\225\245+\0053\300\227T\202\202\016\016\267\304SV\342\342\032v\260_Z4`\223a\312\034\0100\260?\213\200\025a<\372|`\257\316\257<\013<\261\345u\275\377O\224a8\225\317\375\024\"\017\250\217MI1u\206\272\316ZAG\254\375\300@.\266\026\000\000\000\003\272.<\013z1\006\000\320\232\205\215\035\314\221.0,\303\037I\000\000\000}\332\263\325\337s\r\320\000\203\033\000\000\330\340wb\017L\337\003\000\000@\223<\322\017N\002\000\000\000\000\000\020\311w\n\003\214\3516\377\373\361b\333\364\037\000\000\300P\274\312\005\000\000\000\000\000\200\221\370\213\242\000\000\000\000\000\000\000\000\000\000\000\000\320\013\337\'\010\000\000\243\363T\000$\364\037\213Ab\036\317\"\"\006\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_height_map_assets/tile-9.textpb
new file mode 100644
index 0000000..5397cb37
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-9.textpb
@@ -0,0 +1,3 @@
+tile_key: "9"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\363<\322\206\247f\234\r(4\271\2434\271\367\243u.h\rK\272\214\322\356\247\006\247\006\346\244\rO\rN\rO\rN\rO\rO\rO\rO\rO\rR\006\247\206\247\203N\rN\006\234\r8\032\\\321\232]\324\271\2434f\214\322f\214\322f\232M4\323I\244\2444\224QE\024\200\3434\235i\r%\024S\250\242\212up\364f\234\r(4\354\320\r.\3527Q\272\2274\003K\2327R\346\224585<5<5<5<585<5<5<5H\032\236\246\244\006\236\r<\032x4\340i\300\323\201\245\006\2274f\2274f\214\342\214\373\322f\214\320M%6\220\365\244\244=i(\244&\200sKM\246\232(\242\212QKE(\024\265\303\323sJ\r;4f\2274n\245\315\033\250\315(j\\\321\2323F\352pjxn)\301\251\301\252@\324\360\324\360\324\360\324\360\325\"\265J\255O\006\236\r<\032x4\360i\300\323\201\245\315;4f\214\322\347\336\212L\321\232\\\321HM%!\024\224\206\222\212i\245\024\264\332i\242\212(\245\024\264\270\245\351Hk\2074\224\231\346\234\r\031\2434f\214\321\2327R\356\243u.\3523F\352]\324\340\324\360\324\340\324\360\325\"\265<5<5H\rH\246\245V\251\003S\201\247\203R\003N\006\236\r8\032]\324\354\322\346\214\322\321I\232\\\321Fh\242\214\323M!\244\242\223\275-\024\230\346\222\233E\024S\251@\245\242\232k\2104\224\206\214\322\203HM&i3Fh\315(4\271\2434f\215\324\340iA\247\006\247\006\247\206\251\003S\325\252@\325 j\221Z\245SR\003O\006\236\r<\032x4\340i\300\323\263N\006\2274f\235E\031\242\224\032Z(\242\220\322\032LqII\336\226\212)\264\204RQJ\005-:\212CI\\I\024\332CIE\024\206\220\322f\223>\364\271\2434\271\2434n\245\006\224\032p4\340i\341\251\341\251\341\252@\325\"\265J\246\245V\251\024\323\301\247\203O\006\236\r<\032p4\340\324\340i\300\320\016)\300\321E(9\245\247QE\024Sh\2444\224b\227\336\222\220\322R\001\212Z)@\245\242\232h\256,\212a\024\323IE\024\332i\246\346\212L\322\346\214\322\346\214\322\203J\r(4\340i\341\251\301\252Ej\221Z\244\rR\251\251T\324\200\324\201\251\340\323\303S\301\247\003N\006\234\r<\032p4\354\320\r.h\3158S\251\324QE\024\204Rb\212LR\320zSh\246\236(\245\305.(\242\212CI\\{\n\214\212a\024\322))0i)\246\233Hz\322Q\2323Fi3N\006\2274\240\323\201\247\006\247\203OSR\003R+T\252\325*\232\221MH\r<\032x4\360i\340\323\201\247\203N\006\234\r<\032\\\322\346\226\234;S\251\302\212)qF)(\"\223\024b\223\024R\032J)\000\305-\024QHM%\025\313\2749\252\355\031\025\031\030\353M\"\230E%4\323H\246\323i\244\346\214\322f\214\321\232\\\322\346\234\r(4\240\323\301\247\203R)\251T\324\212jU5(5 4\360i\340\323\301\247\203N\006\236\r<\032x4\340ii\302\224S\351\324\3521N\242\227\024\224Rb\223\024PFi\264\204RQE\024\202\226\232z\n(\256}\201\035EB\342\241x\3628\250\031\010\250\310\246\221M\"\232i\246\230i\264\207\255%\024QJ\r(4\352Pi\340\323\301\251\024\324\212jE52\232\220\032\220\032x4\360i\340\323\301\247\203O\006\236\r<S\305.iE<S\305<R\323\251qKE\024b\223\024\224SH\2444\224\206\222\212CHh\315\024\231\2435\222\312\r@\360\372UwB\265\031\301\250Y*\026LS\010\3054\212a\024\322)\206\232Fi(\2444\264R\212\\\323\251\342\234*AR)\251V\245SR\n\220\032x4\360i\340\323\301\247\203O\006\244Zx4\360ii\324\341\326\244Zx\247\np\024\264\354QF(\244\"\222\220\212B)\246\233Hi(\2444\224R\032J+0\212a4\326P\303\232\255$\004Ur\204\036j9W\035*\"3Q\025\246\021M\"\230E4\212i\244\242\212)E(\024\352p\247\212x\355R-J\265*\324\202\236)\342\244\006\236)\340\323\305<S\305<S\305>\234;S\207Z\221i\342\236)\330\247\001K\212Z(\244\305%!\024\323M=i\207\255\024\332)\r%\024\332(\252\014\265\021Zk\014Te\210\250\\\346\240e\3109\250\031\010\250\310\250\312\324dSH\246\021M\"\223\024\230\243\024\264\240R\201K\212p\247\nx\025\"\324\253R-H)\342\244\024\361O\035\251\342\236\264\361R\nx\355O\024\361N\024\361OZ\220\nx\024\360)\300R\342\226\2121M#\024\204R\032a\246\236\264\323IHi)\017ZJ):\320\005-Se\250\312\324N8\250XT,\246\242<S\030\217Ja@\325\023\305\351P2z\324M\221\332\223\031\244\3054\212LQ\266\227\024b\235\212P)@\247\001O\002\236\005J\242\244Zx\251\005<S\305<S\305<v\247\212\220S\326\236)\342\236)\353R(\251\000\247\201O\002\235K\212Z)\010\244\246\322\032a\246\032CM\2444\224\206\222\212(\243\025]\205D\302\243e\250Yj\026\250XTL*\"1I\274\212C\206\250\312\343\202*\027\217o#\2450\212M\264\233h\333K\266\215\264\273i\301iB\322\201O\002\236\005H\005<\nx\024\360*AR\001N\002\236\005<S\305<T\202\236\005<S\300\251\024T\200T\200S\305<R\201K\212\\RQM\244=i\246\230i\246\233M4Sh\240\212LP\005\030\245\252\3548\250XS\010\250\331sQ\262rsQ2T\016\230\250J\324l\264\322(\353\301\246\225\364\346\2431\203\315FT\2126\321\266\227m\033iv\323\202\322\205\245\333N\013O\002\234\005H\0058\n\220\nx\024\360)\340S\300\247\201O\002\236)\340S\305<T\212*E\025 \024\361O\024\361J)\324R\021IHi\247\2554\323M0\323i\r%!\024\224QE\024\240T.*\"\274\323\nSJ\212\201\316\r0\340\365\250\23528\252\345i\245i\205j2\264\336E\033A\351HW\324S\nc\245&\332]\264\273iv\322\355\245\333J\026\234\026\234\026\236\026\234\026\234\005<\nx\024\360)\340S\300\247\201N\002\236\005<S\300\251\000\251\000\251\000\247\212x\247\212p\351N\024\264PzSi\246\220\322\032a\351M4\323IM=h\242\212LQ\212\\QQ\311\307QQ\2023\315D\347\346\"\241c\316\r0\25794\205A\250\210\347\035\252\'J\214\2554\255FV\230R\230W\024\017zw\226\010\342\231\266\227m.\332]\264m\245\333K\266\234\026\236\026\224-8-<-8\np\024\360)\300S\300\247\001O\002\236\005<S\300\247\212\220T\213O\035\251\342\234:S\307JQKE!\244\246\322\032Jm0\364\244\246\322b\222\212(\242\212*2\373\206\rDN)\215\202sLe\3151\224\324y\307\024\322\271\2462\344TEqM+\232C\0354\305Lh\361L)M\332iv\322\355\243m.\332P\264\241iv\323\202\323\202\340\323\266\322\205\247\001J\0058\np\024\360)\300S\200\247\001O\002\236\264\361O\025 \247\212x\355O\024\341N\035)GZu\031\246\223\232)\264\206\222\232i\246\233M\242\212m\024QE\030\250\260\001\246\355\004\320c\301\246\225\246\221\305Wd\301\246\036)\206\243jn)@\247b\220\307\232\215\242\305Fc\246\354\366\243\241\301\245\0034\340\224\241)BS\266R\354\245\333N\333F\332\\S\261J\0058\np\024\340)\300S\200\247\nx\247\212x\247\212x\353N\024\360i\342\234)h\242\212)\264R\021M4\303\326\220\322R\021IHE\030\244\305.(\305-T\rRdu\240\270\357L\334\017CLn{\323\0175\023\000)\207\024\306Q\330\3231F(\247\250\247c=E#F\010\250\214x\355Lhw\366\346\232ad\367\251\025r\005<%.\312]\224\273)v\320\026\227m\033iv\322\201K\212v)qJ\0058\np\247\n}<S\205<S\201\247\203N\024\340i\331\245\242\212)1A\024\206\230i\246\220\323h\244\305%\024QE\025L\214\032c=0\266i\003\342\232_\232\003f\230\355Qg4\034\201MV\311\346\236V\224\n\\S\305<-)L\320\023\006\234\321\002:Ub\2066\351\305<.E8-.\3126R\354\243m\033h\333F\3326\322\342\227\024\270\245\002\226\235N\024\352p\247\003N\006\234\r<\032x4\340i\300\361K\232Z(\242\212CL4\323M\242\223\024\230\244#4b\214PE\000R\342\263\013\346\232M0\234S\013S\013Q\277\024\302\364\201\251\373\251\274f\234\r<\nx\024\340\265*\212xZpJv\323\216)\222\306\032>\225]\007j\227m.\332]\264m\244\333F\3326\322m\243\024b\214Q\212ZP)i\324\240\322\203N\006\234\r8S\305<\032x\247\nZPih\243\024\270\246\232i\246\232i\024\224Q\212n1E\024QF+\017\1774\360\334SI\250\330\323sL&\231\234\232\t\305;w\024\3459\247\014\323\303S\325\215L\207&\246\002\244\002\236\005</\024\320\241\216\017z\202X\274\267\247\005\315;m\033h\333I\266\215\264\233h\333I\212M\264b\214QE(\024\264S\251E8S\205H:\323\205<S\251A\245\035i\324S\250\246\221L4\323\322\222\212)\010\346\222\212)1@\353K\\\343\032r\266E)5\0314\332k\032fy\245oZi5\"\032\234t\246\223J\257\212\235d\350EYI\001\353R\214v\251S\223S\252\323Ja\270\244\236=\313\357P \342\244\333F\332B)1F(\305&)\010\246\342\214Rb\212(\242\212u\024\341O\024\361\332\236)\342\224S\207ZQ\326\235J\0058RR\032a\246\232CIE\030\244\"\223\024Q\336\200=)Es&\2054\342i\204\323sMcH)\343\221\212\215\226\225\016*p\374S\013P\032\244Rj\312?\0305:\260\307Z\2327\301\253\321\020\302\234\313\363S\2312\275*\243.\327\305<\n1I\212LQ\212\010\246\342\220\212LSH\244\305\024\230\244\242\212u\002\234)\342\236;S\305<t\240u\247S\307ZQN\247R\021M\"\232i\246\232i1K\212\\sI\212LQ\214\322\025\346\224\016\324\240W.i\005\004\323O\025\0337\245&iA\247\251\024\244dR\005\243\245!\247-N\270\247n\002\234\262U\210\336\257[\311\203VD\300\232\262\010e\025Z\3456\2604\320(\"\223\024b\214R\021M\"\223\024\334R\021HE%6\220\322QJ:R\212x\353N\024\361O\024\361@\353O\035i\302\234)\302\224QM4\323M\"\233E\024\242\214Q\2121HG4Q\326\271L\323Kb\233\273&\232\317\315FM\000\323\363H_\025*6Fi\331\246\223@\245c\201M\363qHe\315H\222U\210\234\325\270\345\"\255\246H\334*\314R\236\206\211\244/\317\245*\034\212~(\305\030\244\"\223\024\322)\010\244\3054\212f)\010\244\246\322\021IJ\0058S\205<S\307Zp\247\322\212p\247\201N\024\340)\330\244#\232CL4\322)\244Q\212Z1K\2121F)\244P\005-q\346\233\324\320\307h\367\250I\246\323\226\245\003\212c\016jU\030\024\356\324\334\323\226\221\307\025\003)\246\200sS\306*\324b\255\306\271\305[\214\225\\T\360\214\346\247\n\010 \365\250\323\344}\265`\014\322\342\214R\021M\"\232E4\2121M\"\230E4\212i\024\322)(\242\224S\3058\nx\247\np\247\nx\247\212p\247\nZ)\010\246\221M\"\233\212JP)h\242\227\024\322)1K\212\343\013f\232[oJc1=i\271\244\247%L(\306M<\nZi\247-\0148\250\217\"\220/52\361V#Ry\253\221\n\266\243\002\254\300\234dT\354\234dT2/\000\216\325,|\201Rm\243\036\324\230\246\221M\"\233\2121M\"\230E0\212i\024\322)\244Rb\224\nZp\247\212p\351O\245\024\360)\342\236)\302\235N\244\305%!\024\322)\244RQE/jZ)\010\244\242\270|\342\202A\2461\246\347\232Zz\361R\nx\247QM<\323\322\207\351Q\nv)\311\311\2558\020\005\025aTT\3123Va;j\354 8\'\265C,xb;S\"\033X\255O\2121HE4\212i\024\334PE4\212aZi\214\216j2)\244SH\244\242\224\np\247\n\220R\342\235O\024\341O\024\361N\024\242\212)1M\"\232i\270\244\242\235J\005\006\222\212+\200-\315\001\251i;\323\205H\007\024\341O\035){P)\017Z\221(z\217\024\247\245I\002\026`kN1\200*\302\324\312*e\025j\014\203O\230g\004Uc\303\203V\227\221F)1M\"\232E7\024\021M\"\232T\0321\306*\006\\\032i\024\302)1E.)@\247\212p\247\322\201O\024\360)\302\236)\324\270\353F3@\024\224\323M4\332\r \024\264\341E\024\230\243\025\347D\363NZx\351KJ*U\247R\203JM\000\322\023\315J\235(\316M&)\247\322\257Z\307\205\031\253\212*d\025aEL\203&\254\'\007\212\224\214\212\254\313\301\366\251\3429QO\305!\024\322)\244Rb\233\212i\024\322)*6\03750\212a\024\230\243\024b\234\0058\np\024\341J\005<\nx\247\212p\247\nQE\024\206\232i\204R\021IE(\024\264\270\342\200(\305\030\2577\034\323\201\247\203N\245\035i\340\323\301\247\nF\342\2054w\247\226\302\323Q\262jZD\\\2775\243\021\342\254\245N\202\245\310\002\244G\253P\363\315X\306EVu\301>\364\260\235\247\006\254\342\202\264\322\264\322\264\334SH\246\021M\"\223\025\031\034\323H\246\021I\266\223\024b\235\212p\024\340)i\300S\200\247\212x\024\340)\324\275\250\305\030\246\232i\024\206\233I\2121KN\002\212P(\305\030\2575\002\236\0058Q\232\\\323\225\252@i\371\305!9\245QK\212\033\221L\037+U\205\344S\200\301\253q\036\225q\010\035iL\276\224\345rjt\255\033q\2003V\2052H\362*\020\207>\365:\036\306\244\3054\255&\332i\\Tl)\204SH\246\221M+M+M+M\333I\2121J\005.)\300P\0058\nx\024\340)\342\234)\300R\322\342\222\232z\322\032i\024\204Rb\214P\005-(\034S\200\245\244\305y\270Z\\b\212B9\2434\341\326\245Zu\024\364\024\204\363N\034\212FL\362)\312\330\353Rn\315O\023\0003\336\245\016O\322\246\217\236\265aMX\213\226\025\245\017J\262\265&2)\0259\351Oh\3062)\000\310\240\2554\214Tl)\204SJ\323\010\244\333M\333HV\230V\220\2554\255&\332\\R\342\214R\342\234\0058\nx\024\340)\340S\200\245\305\024\021M4\322)\270\244\305\030\240\n1K@\024\360)h\305y\2304\354\346\232N)A\315\004S\220z\324\271\300\245\003\024\243\232\223\240\246\232QS\001\305\030\024\030\373\212|C\'\025d\214b\245F\253\0108\311\253p\0163W\2438\002\254#T\312\325\"\363S\371{\223\"\242\t\212R\265\033\naZaZiZaZ6PR\230V\230V\232V\223m&\3326\321\2121@\024\340)\340S\200\247\201N\002\235\212\\z\320G4\204SqHE&)1F\3321F(\305(\024\340)qF+\313\305:\233\324\323\207\024\340sN\024\341\326\234[<S\201\30585\004\322\216\265(<S\224S\311\342\235o\313\223V$\351Dun3\221V\340\340U\3055:\032\225M<?5j\t{\036\206\245e\364\250\272\366\246\225\246\225\246\2244\302\264\233)|\272k-F\303\025\021\300\246\344QF(\333F\332M\264\273iB\323\302\323\200\247\001O\013F=)q\221JG4\322)1I\212L{Rb\227m&(\305\030\243\024\360)qF+\312\305-8R\232\000\251\000\300\240\236\324\243\2123J\032\236\r(5\"5H\r9\217\024\350\016\01754\215O\214\325\250\210\365\253\221\034U\244j\231Z\245V\245\316\rK\024\234\214V\222r\242\230\340\006\351H\303\212M\224\322\264\322\264\334\000ipOALh\237\322\253:\234\340\320\266\254\3434\326\262n\306\241h\335\017\"\220>:\361O\0074\360)vQ\266\235\262\224-8-8-;\003\246h\372R\201\216\224b\220\212LRm\244\305\033h\305\030\244\305\030\245\3058\n1K\212\362\214\322\212~8\245\002\236\006(&\221FNM8\2650\265&\352xzQ\'\255=^\245\022\nz\276jd \032{\216\364\364n*x\233\201W\242|u\251\325\252tz\225[4\254\330\2536\213\274\346\264\320\342\221\316\347\030\243\024\264\306 S0Z\244[|\363R\254[y\247\272\251N:\325#lY\262EZHF\334T2\302\312x\025]\243\335\324Tmh\030t\252\222@\321\036(G\365\253\n3N\tN\331@J]\264\270\243m\024\270\245\000\032B\264\233i\010\244\305*\216\264\230\240\212LQ\212]\264\340(\305\030\257%=jTZy\342\223v(\r\232w\035\351\245\361\322\243/L\336(\337\336\223\314\315H\032\246\215\207J\221\030d\324\350A\251\224`\324\200\3664\231\332\246\247\203$f\256+p*tj\2305L\217O-\232\275b\3406=kQ\024\036\225\033|\214sK\221Mg\364\241\020\267&\254EnX\360*\372[\205\030\"\234b@9\305FD=\360*\264\262 8Z\256\323m<S\326F\223\036\225(\205Xt\346\206\266\300\371j\264\266\233\263\221Td\262 \344\n\215T\241\346\247A\232\227e.\312M\264\322\264\230\243\024\270\245\333AZM\264\205i6\322\250\000\234\322b\215\264\230\243\024b\235\2121F+\3111\315H\274R\232h\031\247\022\0050\275F\315L\'4\302i\246OJE\220w\247\254\303\326\244YT09\247\254\336c\341x\253h\330\034\032\262\262|\2714\261\311\274\373\324\2238\001GsVm[\367d\032\235\0375*\271\035*d\223&\254+S\303\324\360\273\0021[6\363\344\014\365\253C\347\3523A\210t\002\221m\035\317\3355v\033=\277z\254\210\302\014\360\005C-\317e\252\31730\250\031\232\252\276\342i\2715<R\221S\213\254\034T\3510r0qW\002\006Q\300\346\230\326\201\272U9\3541\310\025I\255\3323\2208\247\'5!^)\205i\245i\245h\013K\266\224-\005i1I\266\215\264\230\306i\002\322\355\244\305&)qKE\030\257&\331\212pZk\373Sz\n\214\234\323\t4\302Oz\211\346\003\201P\371\244\236i\371\310\250\217ZQOZz\261C\221W\"\23389\347\275X3\340\343\265Ii \363y5=\311\314\253\212\263\013`}ju|T\252\364\365|\032\230MV#9\346\256F\330\034\325\353yFEn[H\205GL\325\245e\'\370sR\371\321\240\371\216\343\350)>\322\010\302.\rC$\245\270&\253\021\232M\264\322\225\023\307P\024\240)\024\354q\232\221\t\035+F\336^0\335*\364d\036\225ab\0140\302\240\237O\005IQX\3676\215\021\334\005B\215\232v\332B\264\322\264m\243m.\334Rm\244\333AZM\264\322\274\322\343\024\323\3054\232M\324n\245\315\'>\224\273I\257+lRg\216)\204z\324nj\026`*#%D\362Uwj\214\266)\313%?viA\247\006\305(z\221\037oJ\262\254\n\362y\244Y\2126Gj\320\211\314\307y\364\253L\333UO\255>9sS\253\322\371\230\245\216B^\267\355\255\013@\035O\024\004!\261V\243!:\346\256\3013\266\002\212\324\2066#.j\177\335G\313\234\232O47B\007\2654\234\367\243\024b\227\024\322\200\323\014T\337*\236\261\002)D\035\305H\250E_\266\343\025}\010\317<T\340c\336\253\\[\t\201\342\271\313\270\r\274\247\035(\214\206\034S\212\322\025\244\333N\333HG4\025\246\342\220\2121M\300\250\335\200\250\211,x\247\010\031\251\337g#\255 \217\232\221a8\247yT\206<W\224\025\036\224\323\201P\271\250\035\260*\263\022MF\306\241cQ1\250\215 5\"\276)w\322\356\247\006\247\207\251\222BF\321Ly\n\235\244V\345\236\014JE>v\373\253N\210\360*\33251\337\232\265n\200\200Ml\333]\210\343\nNG\245)\273\313p1Wm\246\014>`\010\255{f\207\031R\006:\324\263_\240\033R\253\t\313\036Nj\302>ju\346\237J\r\007\353J\r\024\340\231\247*S\300\301\251\000\366\251\020\340\325\373e\336G5\243\345qQ:\n\310\324\255<\300N+\017i\205\275\252\302:\260\340\322\221I\200)\t\024\231\024\322\342\2432\016\325\031v=\005&\347\364\246\222\347\265\013\003\261\311\253Q[\205\353S\355\013Ma\236\324\301\037=)\341\016)\014f\217.\274\204\212\211\205B\374UW\3115\013qQ\265D\325\013\na\024\323I\232\\\322\356\245\rO\rOI6\266jI\037p\007\336\266l\234yC\236\224\347\270\005\261\217\306\244\216L\324\302LSD\233\237\025~96\000\005Z\215\213\n\265o\t\221\376n\007z\272\322*\r\211NIXp\rL\256OSVcj\265\033\342\254\244\2250z]\364\273\251U\252@i\353RS\200\006\236\242\237\264\324\360\314b\"\265\355\356\026T\301\373\325#GU\346\2040<V%\345\230\311\300\254\326\264pr\271\024\236T\302\233\344\316{R\213Y\217\\\323\276\303.y\3158i\316z\223S\307`\243\250\315Y\032x#!x\2456 \034\025\250\332\311Gjg\223\216\202\220\300~\224\242\020:\322\024\002\220\250\024\224\323M\257\0365\023\036*\274\207\203U\232\241j\214\364\250\332\243\"\243\"\230E&(\305%\024\340iI\251\003.\337z\277gq\225\353\322\234\322\345\252\344-\305L[\212\215$\303\326\234\r\270\014\326\214X\002\255\tx\302\360)\312jej\231\036\254F\365e\036\247G\251\203\232xzxzP\365\"\266jd5.}\351\312\325(5\"\234\365\245#\232\275g(\215\206k\\:\272|\247\232\211\205A%\272\277QP\265\232\366\000\325w\266\307E\240D\000\345)\2050\331\002\246\t\274r)D\002\203\010ZM\341F\007J\202I\262x\246\371\343\0375D\323F\017\024\217.zTe\252\026j\214\275(9\2434\332\362\006\025\013\212\251)\346\253\275DE0\212a\024\302\264\322\264\302\264\322\264\205i\244SH\305(\245\305C<\276Z\222\016)\332d\345\263\223\326\264\201%\271\253\321>\005J_\212\213v\0335\255k\'\356\324\326\2042f\255!\251\224\323\267\372T\250MXF\305YI\005L\263\001R\254\242\236$\247\t(\022T\311%N\222T\242Z\221^\246G\251\224\346\245Q\2322T\3475b+\355\204\014\326\2147K(\031\353S\343\214\212\214\322\034S\n\003Q\030\2114\360\240S\361\212\2574\300dt\252NI\031\rU\244W\316s\232\202\\\2163LRE;y\0244\231\025\02350\265(zv\354\320My\023Uy\033\322\252?Z\205\2050\2554\2550\2554\2550\2554\212a\024\323L\"\232i\264\352\315\324$\302\220*\326\212\204\214\236\225\253\374uj3S\003\305D\347\006\264\355\030\030\226\264\242\224\001\201V#z\260\255R\241\031\353S\254\201{f\227\314$\344S\326CS\243\023S+\021\326\244\022S\303\323\203T\210\325:\275H\262T\213%X\216J\265\033U\225aMs\236\225]\301\007\"\254\332\335la\232\337\212ex\301\0074\034\032a\024\200\322\346\220\220*3(9\025J\347\'\221T\335\231V\242\016O\336\244`\r7g\025\033\014Td\323\r4\232@h\017\212R\365\344\2621\252\316j\026\031\246\225\246\025\246\221M+NX\201\004\261\305Wu\347\212\210\212a\250\3150\323\t\244\243\265f]\246\351\000\255{\030\374\250F:\325\241\367\252t5:\322\262f\255@\031\000\364\253)!\0075r)\263\336\256F\371\251\343 \036j~\033\356\324\253\220=\351y\0075<s`sN\363A\247\007\247\211)\302Z\221f\307z\224M\232\221d\251U\352x\344\305]\212^*e\222\246S\221Lz\205\362\274\325\375:\364\203\265\217\025\246\263\202z\323\314\312\007Z\210\334(\353Lk\324\025]\356\331\363\216\225\037\236Oz\212IK\036\264\306pG5\0162j@\243\034\322\021\201P\265B\324\323\315Fi\271\244&\232My[\324\014)\273x4\302*2)\244Rb\232\335*&\025\023\n\205\252\0264\302i\244\321JzV|\347\367\2035\253f\013F\t\253\241EH\270\251\227\024\375\352*\304rdd\364\246Ip\027\201V-\244\316+J90\005YG\3175b9MXY)\333\363F\352xjxz]\364y\224\tjT\224\325\210\336\255\306\331\251\324\212\263\033U\205z\231d\305!|\232q\033\326\230\243\3139\025g\355g\030\357A\271\'\2754\316i\245\311\245\016i\333\251\214i\264n\300\342\223\314\315!zal\324Li\264\323\315Fx\246\223M&\274\265\2522\264\230\3050\2550\2550\2554\212c\n\205\252\027\250\036\241ja\244\245\002\234G\025F\346>sW4\351\2066\261\300\025\246\010<\002)y\024\354\232P\244\375*O7h\300\252\345\213\311Zv\274\001Z\010\365z\006\335\305Y\300\035)\003\324\210\331\251\001\247\006\247n\244\337@|\323\3075\"\266*\302I\212\263\034\265a$\253\t-X\216J\225_4\375\325\"59\2153\275<c\024\022(\016)\300\323\267SI\246\223M&\230M4\265!4\231\2444\303Lja\024\323^`E \000R0\310\342\243+\212a\025\033\na\250\232\241j\205\352\006\250\310\246\221M\3058\np\\\324\0271\344b\250\020\361\234\212\261m~\361\261\3632kF\rAX\363W\005\302\205\310\002\241{\242N)V\\\212\236\021\223Z1\014\n\262\215\212\265\004\2705o\315\342\220?5\"\311R\211)\302J]\364\273\350\rS#f\235\272\244F\2531\275XW\251VLT\361KV\222Q\353R\t\005J\262S\367\344Rf\205\223\024\374\203M\350i\333\351w\346\215\324\322i\244\323\t\246\346\2234\271\244\242\242q\212`>\264W\231\225\246\221M\"\230\302\243\"\243e\250\332\242aP\260\250\\T,)\204SH\244\333J\026\244\013\212\216E\315Wh\001\250M\267=(0\024<U\310\203\005\000\232q\214\346\247\211=j\324\177)\253\321\276EN\246\246F\305N$\247\207\315<=J\257\305/\231N\363)C\323\203\324\321\2658\2775,oVQ\252Q%<IR\307.OZ\264\254}jt~9\247\2111R\ti\342L\364\245\316i\341\360(\363)\013\322\206\247n\244\335M&\220\232J(\244\006\202qMnj\"0h\2578+M+L+Q\260\250\330sLaP0\250\330TL*\026\025\021ZaZn\3326\323\325i\032\243\306z\323X`PW\214\323H\311\025\"\234S\267\323\225\352\302\034\325\250\016*\342\232\231N)\341\205<0\247\253T\201\2517S\267\323\325\362je\347\255I\274(\244W\311\251\325\361R\254\2250~)C\346\244W*\302\256\307>@\251\204\331\2453zS\322\\\324\352\365 z]\364\240\320[\024\t1N\017\232]\324\205\251\273\251A\247f\226\233Fr)\244\342\230Ni3^zV\232V\243+Q\262\324L*6\025\023-D\302\243aP\262\324ei\214\264\335\264m\243\024\322\271\244\333\212\215\305+\034\001L\003\234\324\200R\021J\2654mW\"j\266\207\"\234d\305\002\\\324\252\365a\017\024\354\321\232P\325\"6*p\331\034SY\215*\275J$\251VJ\235d\342\236\262`\322\274\26543\347\203S\211H\247\2113S#\342\246\022T\202Zw\235@\233=\351|\312O2\234\262{\323\304\224o\245\017OSO\006\2279\244&\243\'\232B\324\302i7W\020R\243+Le\250\231j&Z\211\226\243e\250\231j&Z\211\226\230V\230R\233\262\223m&\3326\323J\324,\274\323\030f\200\265 \024\021J\0059\001\'\212\267\020\351V\323\245)\024\321\301\251\220\324\352\324\360\324\354\321\232p\353\305N\206\225\31538\247\253T\252\325*\275J\0374\355\331\251\"85d7\024\252\325:\311\305<IN\022\342\227\315\245\363h\023R\211sR\007\247\t)\341\251\352jEj\2245.\3523Q\263S\t\315F\317\212a\222\271r\225\033%D\313Q2\324L\265\023-D\313Q2\324l\265\031Jc-FV\232V\233\266\232E&)\010\250XS6\322\355\247SM\024\36485n6\351V\220\346\224\232eH\271\251T\324\200\323\301\247\212p5 <SK\346\220\032x5*\324\200\324\210jPjDlT\241\251\301\252P\334S\303S\267SK\342\215\364\241\251\352\3252\275<75\"\236j`i\331\251\025\251wQ\277\212\214\266i\204\342\242w\250KV;-DW\332\243t\301\250\231*\026Z\211\226\242d\250\331*&JaJ\215\222\230R\232\313L)L+M+L\"\230V\233\266\223\024\230\246\221F)EH\214E[\211\315N\334\212h\251T\324\213\315H\005H\005;\024S\351\273y\247\001O\002\245Zx\251\024\342\244\006\2245J\255N\335\315L\215\232x4\271\246\226\244\335N\rR+T\212j\302sS(\342\234\033\265\005\251U\351\333\3517R\347\271\250\335\352\026j\210\265Qd\250\331j6N9\353P\262TL\225\023%F\311Q2Tl\225\031J\215\222\230R\243e\246\262\324ei\205i\245i\205i\214\2704\322)\204b\214Rb\235\212U\025:\034T\313\'cO\034\324\212*U\251T\324\200\322\356\245\3158\0323NZx\247\347\024\241\251\301\351|\312<\312\225$\251\224\346\246V\305H\032\224\2654\323i\300\323\301\251\243oZ\266\203\214\324\201\261N\006\224\234\324d\340\323\203f\234\010\035i\035\270\342\240g\250\331\251\205\251\205j\"\231\2462\324,\225\033%D\311Q\262TL\225\033%F\311Q2sL)Q\262S\nTei\205i\245i\205*7^j2\264\322)1F(\305(\025\"\324\212\271\2531\245K\267\024\n\220\032p4\341N\006\227u\004\320\254jA%.\362i\273\311\247\253\023R\014\232xZ\225\026\246\034S\267b\234\257\315)j\004\225 `i\331\245\006\244C\212\266\217\305K\270S|\316iZN8\244\031\357J[h\250\314\246\2173\212c5FZ\2235;&j6J\215\222\242d\250\312Tl\225\023%F\311Q\224\250\331*\"\225\031J\215\222\230\313Q\262S\nS\nSJ\324L\265\033%1\226\223m&\332\n\320\026\236\005J\202\254\245I\232\000\3158\nv)\302\235M4\240\323\205\004\322\203N\006\236\rH\246\245SN\335\212Q%;vi\312i\371\315\000T\200T\213\315?\024\341R\253\340T\201\263N\343\326\234\010\245/\212k8\"\240\335\315.\352Bi\231\2435\242R\243)Q\262TL\224\306J\210\245F\311Q\262Tl\225\033%BR\243e\250\331*2\224\302\225\031ZiZ\214\2550\245F\311Q\272\323v\321\266\215\264\233)\301jE\030\251\024\342\237\232@\3305:\020\325&(\002\226\212a8\243u(4\340i\324\3655\"\324\253N\306h\332i\301H\247\250\247\347\024\241\252e\251\007\255H\2434\360\224\275\005*\265\005\350\3631A\223\"\233\272\233\236i\331\244\3154\320\rm\024\250\312Tl\225\031J\215\222\243d\250\331*6J\211\226\242aQ2Tl\265\033-D\313Q0\246\021L\"\233\266\232R\243)Q\262S6Rm\245\333F\332\002\323\202\323\302\323\266\322\025\247\247\006\254\216E8\n\030T\014H4\204\346\201\326\234*E\024\354S\324T\310*P8\243<\324\213Rb\214SM*\324\240\342\244\00752\n\224P\335\351\203\212k5F\033&\235\232M\324f\234\0334\271\240\322W\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\"IDATx^\355\335\333\266\342(\020\000\320\263\234\377\377\344q\315x;G1\367\020\002\324\336Om4\tP\005\001\264\273\177~h\307%=\000\000\000\000\004`G\000\000\000\310\315:\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\345/\013\003\000\000\000\000\324\315>.\000\000\000DdG\000\000\000\000\000\000\000\000\240]~\373\001\000\000\254b\021\001\000\000\000q\\\323\003\204\364\177\036\\\345\002\313\330=\214N\006\000\000t\3114\017\000\242\2623\314\274\213\311bl\022\000b2C\210I\334\001\000\000bJ\326\203v\004[\267r\201\177\013\370\312S\250\232h\002\000\000\000@\\\266\370\001\000X\301\3641\256{\354%\000\000\000\324+\347\277\362\221\363Z\344\347\267\337\214\320s\203\223\000\020\227\376\017\000\221\331%\000\350\226!\036\200\233\322\317\203\322\367#\221\006 }\235\231\315e\000\000\000\200\340\016\336\177\002\000\000\340 \326s\237\242\374\000\"S\3343]\006\000\000\000h\202\235\000\000\000\200p\236\337\241G\371*\2357\266\001\240\t\272*\000\333\230\341C([\273\374\326\363h\335\353?\233\277Zo\004\'\001\242\223\001\301I\200,\314\246\000h\215g\027\000\000\000@\313\354\356\000\000\000S\374(,\2364\346\351k\000\000B0\r\004\010\353\342+\344\310D?8\t\000\000\020\203\255\277\350~3\300\022\000\000\272c\246\307:2\006 \212\333\026\200Q\237\317\034\2601\004\000\315\362\030\007V\353vE\330m\305 \247\367\216\242\323\304!\326\335\261\n\000\000`-s\310\270\304\036\"3\002\000\000DgF\030\214\357\205\203\223\000\020\217\'=\000\360\306\324\240m\342\007\000\360db\024\234\004\240i\333\276\255\333v\326\235\036\323\236#b\266#\205\340|Gt\n\000\200\354rNZ\314\340\033\2645\001\266\2367\355\230\253RJ\215C\200\234\002hC\215\317\020\312\362\314\216N\006\004\367L\000y\020L\032\360\3645\264\3004\026\200\332\205\231c}U\364\353\000\347\021\014\010n|\335\264kx\030\277,\237\216j\251\r\341\033:\345\250\342\0015\032\032\005&\031\"j\366\027\316j\343\264:\343\350\313\241\tp\350\305Y\244\332\241\007z\226\253\343\355\271\316eh\004\23696\3646m\022\313S\355\351\2720+C\202e\270D.F\253\315\036Q\254(\226\034+\355+[B\177;g\313yT\350-\220b\332\274\264{\007P.k\017j\334W\005\376*\222\365F\213/\366\371\301\301-\200\345\336\316.\027\240\266\034\325.k\003\267\366\363\017\327G\361/\267?\354\314\025\010\355\264\356\263q\010z\225\367\275\334\317\361\000\210\313(P7\361\251J\301\'\377\\\3443\025e\3566a\2156L\246\206_\351\25589\013\220\363Zg\030\215\322N\023\355r9\352\236s&\312DnU5\366\2430\037\313\267\267?\347UU\305\3330\030\214\301\203\313\315\237\2763P;O\347\333|\314\026\273G\347~\275#{\375d\016\330!Z-\177\213\215E(\377\235\330\357\370\250<\362\341\370\373\260\322XG\205\355^Yu\371\353\363\217I\301?\333\307\200\3475G\022\366\357pz\203\221\023\342\032i\220\221\303\204\223\366\240\212\205N\332\232\3434X\266\301\203\031\035}\375\316u\333|\335V\354t#-;r\370\347\347\237\364@)\243%\"\261\252\245^_*\255:\351h\203\205\031<\310\200\365-\025z\026\326\201\201\345\372*kN\272\337+G\302\244\327x\177\235\276\307\264{\000s5Z\235?\323/V\250\341\316P\354\366\333\014\027z\205\327\267n\225\327\223#}\007\377\324/c/i\201\316,\014P\232\036\177\256\357\'B\020\022o\204\206i\317\242\230]\236+\277\267\017?W\202\'\016\002\023\267\236x\213E\021\037uo\332\312\332w\2668\373\252\314\237\340-9\233i\255Z\\\261SW\375;,,\366X;,<=\214z\333cc\311\346N\273\275?\367\231V\215%}O\216\036\267\016\276|niq\353\374b\'\217\216\253\266]\232\000[i\3346=\306\303g\364\326\006\361\231<\327\333E\326\236{\214\301ei\372z\314\327\227I\237n\225\374\374\300\324\253Pj\257\372\\\371\026\217\202\237\027\372=m\361\371\375\231k\332>\254\255\345\332\317\323\210\300=}Xo\r\322[}8\214T\201\272\035:\021\233\272\370\324{\034cn@\236{\177\316i\273\243\005\357[\360V\345$\201\337\233\007\257m\263\304\3201j$Rls\037\036\273\034#\013i\275\353\365\036\373\326\343\303N\317\004\350)\017~\353\322S\245\250\305\340b \210\337\252\367\376\\<\303\231y\2253\251\037\227\272\234[\241\r\032+.p4\203B\007\0041<)\000\254d\235\373-c\233\344\\v\316\333R\360\353\343\311\361*g\321\362\226\266\265r[\317k\307\226\314\351[\3771gJ\323\361o\272\360\203\014P\253\364\227\000\265Jfx\265\344iw\t\320]\205*QK\302Nk\243\2244\301P\302\"i\242\224\032\2052\335\'\323ez\244i\356\322\004\207\030\226\016\000\317\317\351(\301I\000\000ZP\366\207\007wK\247T\243N+\362X\311G\217\217\275\001\264\245\374\250CU$\000\363<\362\001\272t\375\367\371C\370e\032~\032\254\250e0\227[\323\274\375\017\022\327\206\243\014l\363\335\355\215\231\000\224\366\3754\002\330\302L\026\240\010\303-\000\205y\364\000\000\204\347\357-\001\000,c\332\004\020\234\357T\000\000\370f\273\200A\022\003\000\200\305\354=\003\000\000\000\000\361\330\031\355\204@\002\000\000\300\371\254\317\001\000\200I\345\376\322\263\345IY\325\265wu\005\nF\373\003\000PX\271\325f\353\252\233\254WW \000\000\000\000\000\000:\344\013E\000\000\000\000\000\250\312\177]O\370\366;\244\233\330\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_height_map_assets/tile-b.textpb
new file mode 100644
index 0000000..b04a194
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-b.textpb
@@ -0,0 +1,3 @@
+tile_key: "b"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\310\315-\004\346\212(\245\315-\024\003O\006\234\r;4\273\251sFh\335F\352v\374\014S7SKRn\243}(jP\364\340\364\341%<I\357N\022R\371\264\276m;\315\367\245\022S\204\224\3572\227\314\243\314\243\314\251c\2235)l\212Bx\254\373\221\345\266}j5|\323\213SKS\013Rn\240\265\033\2517Q\272\233\272\224\034\325\210\370\024\354\322n\244-\201Q4\300TfqQ\231\3523=!\270\250\332\350Uv\271$\325\243I\232\\\321\2323Fih\315\024S\201\247\003K\232PisK\232Bizu\246\226\246\226\244-I\272\223u.\3527\322\357\245\017K\346R\371\264\242JQ%H\254M<\023N\334iw\032]\364n\243}H\222b\247\022\344u\240\311\216\365\005\313\007_\245T\007\322\235\272\230Z\232Z\233\272\227u\033\2517Rn\240\232\222>\265eM\014@\250ZP;\325y.3\300\250\014\204\323K\323\013\323\013\324l\365\0235D\317\212\326\242\212(\242\2123K\232L\322\203N\315\031\245\315.isK\232\032L\214S7SKRf\223u!jM\324n\243}\033\350\337F\372pzp~j\302?\025&\372M\364o\245\337K\272\223u(z\221d\247\027\310\252\357)\246+f\224\265F[\024\205\2517Q\272\214\322n\245\rAl\232\2323\201Ry\230\250%\237\035\rViI\357Q\226\315&i\t\2463Tl\325\031j\215\232\242c[tRf\214\321\232J(\315.h\315-\031\245\315\031\245\317\275(4n\3054\2654\2654\265&\3527SKRn\244\335I\272\215\324\233\350\337OS\232v\354S\326lT\202l\322\371\236\364\242J\220IK\276\215\324\233\251\301\351\346L\n\202Bv\223H\207\212R\325\03357u\033\250\335F\352M\324\273\250\335R\tp)\217)\250K\023IHM4\232i4\302\325\033\032\214\232\214\232a5\273A\246\321E&h\315%\024\271\2434\264f\2274f\214\321\232ijajn\352M\324\233\251\013R\026\246\226\243u!jizM\364\365\233\024\246\\\322y\224\242Zp\232\244YI\357R\254\224\361%8=.\352pj\035\270\244V\334\244Ss\216)\245\251\205\251\273\250\335J\032\215\324u\245\346\212Bi\271\244\315!4\204\323I\246\023Q\223Q\261\250\311\246\023L&\272\n\r6\212L\322QHh\315\031\244\315(4\271\2434\243\212L\346\202i\205\251\205\251\245\2517f\220\2650\265&\352izM\364\205\351\205\351\273\350\337K\346R\371\224\273\351C\323\226J\235e\315J\036\236\036\234\036\234$\244y:sJ\222\000iX\367\024\302\324\302i\271\245\315.iG4\361J[\212\215\232\2234\204\322f\222\220\232a5\0314\302j64\302j2i\244\327EHi(\246\321E!4\334\321\232L\322\346\214\322\346\202\324\200\322\026\2463S\t\246\026\243<SKS\013SKSKSK\323\013\323w\322\027\244\337F\372<\312]\364\340\364\242J\231%\305L\262\324\213%?\314\364\240=#>M9^\244W\310\347\2654\2674\322h\315-.}i\333\370\342\223u\033\250\244\315!4R\023L&\230MFM0\232a5\0314\323L&\272J\017Jm\006\233E!\246\232JL\321\2323Fh\315\004\320N)\244\324d\323I\3053=\350-Q\226\246\026\246\026\246\226\246\026\246\226\246\026\246\227\244\337I\276\200\364\355\364\273\351|\312Q-H\263\221\336\247I\263R\254\224\340\364o\346\244W\247\253\340\020)7R\356\245\315.h\006\2274f\224Q\232L\321\326\202i\244\323\t\250\311\246\023L&\230M0\232a\246\232\351h\2444\224\323E!\244\246\322\032J)3@4w\244&\232M0\232c\036\324\204\323\t\246\023Q\263Te\251\205\251\205\251\205\351\245\351\205\351\273\350\337I\276\227}.\372<\312]\364\241\352T\223\336\254$\324\363/\245*\311\236jUz\2326\371I\245\315(4\340is\315.is\3158R\346\220\232\000\240\232i4\302i\204\323\t\246\023L&\230i\246\230M0\327OE\024\204b\222\220\212i\244\244\"\230h\244\244<P\017\024\236\364\204\323I\246\023L\246\223L&\243cQ\226\250\331\2526j\214\2650\2650\2750\265&\372iz7\322o\245\337F\372]\364\340\365\"\275H\257R\253f\246CS\003R\253\343\212x4\360isN\006\2279\247\201KFi:\322\346\2234\322i\204\323I\246\032a4\303M4\323Q\232i\353]0\245\242\212LRR\021\212i\246\232a\246\232)\t\243\266(4\323LcQ\223\232Bj64\302j65\023\032\214\232\211\232\243-L-Q\223M&\220\232ijM\324\233\250\335F\372]\324\360\325\"\265J\246\246SS\306\325:\234\014\232\"$\2615`\032p4\360iA\315H\264\354\322\023Fh\315\031\244&\232M4\323\t\246\232a\246\232i\246\032a\246\036\365\324QK\2121IHFi\r4\212i\024\303L4\206\232i\302\203L&\243<\323I\250\330\323\t\250\330\324Lj&5\0335D\306\243&\243&\232M0\232B\324\322\324\334\322f\214\322\346\224\032\221MH\246\245Z\231jelu5 \334\374t\025:|\243\025(4\360qN\0074\360i\333\251wQ\272\2234\271\2434f\232M4\232i\246\232i\246\236\264\303\326\232i\206\232k\247\245\002\226\212)1IM\"\230E0\212a\246\232h\031j\220\323\032\243cQ\261\246\026\250\313Tl\325\033\032\211\215F\306\242f\250\311\250\311\3150\232c\032a4\322i\244\322\023I\232)sN\006\244PML\213\212\235EJ\242\246LT\240\323\303T\212qO\rN\rN\rJ\032\215\364\273\251sJ\r.h\315%6\233M4\206\232i\246\230i\244S\ru\000R\321E\024Rb\220\212a\024\302)\204S\010\305\"\214sJM0\232\211\215BO4\306j\214\265F\315Q3Tl\325\0335DZ\243-L-L-L-L-L&\220\232Bh\315\002\236\005H\270\025 j\221Z\245V\251U\252Uj\2205H\255\212v\372P\364\340\364\340\324\355\364\241\251\300\323\201\245\006\235\2323HM%%!\246\232CM4\303M4\303]E\024\nv)\010\243\024\224\206\232i\204S\010\250\330Rt\246\223Q\261\250]\252\"j2\325\0235F\315Q3Tl\325\0235F\317Q3\324e\351\205\351\245\351\245\251\273\2517Q\270R\027\002\200\364\242J\220=H\255R\253T\252\325*\275<IR\007\305;\314\247\007\247\207\247\007\247\207\247\006\247\203O\006\234\r8\032vh\315\031\243\024b\222\232E!\024\322)\204S\r4\212\3521F)h\242\212)\246\232i\244S\010\246\021\315Fj65\023\032\205\2175\023\032\205\232\242f\250\231\2526j\211\232\242g\250Y\3526z\214\2750\2754\2754\2757}4\312\0057\315\317J\003\323\203S\203T\201\252Uj\221Z\244\017R\007\247\207\247\211)\301\351\341\351\341\351\341\252@\324\365j\221MH\r<\032p4\340ii@\245\243\024b\232E!\024\302)\244S\010\246\221]=\024R\342\222\212(\246\221L4\302*6\3435\0215\033T.x\250\030\324.j\0265\023\032\211\232\242f\250Y\252\026j\211\232\243f\250\313S\013Te\351\205\351\215%G\270\232z\265(l\323\303S\324\324\201\275\352Ea\232x\223\322\236\257R+\032P\3478\024\375\304S\225\352A%H\032\234\034\346\247V\251\024\324\252j@jAO\024\361N\002\234\005(\024\270\244\"\220\212i\024\322)\204SH\246\021]-\024\242\226\212CA4\224\207\2554\323\010\250$<\324MQ1\250\\\324.j\0075\003\032\205\215D\306\242cP\261\250\035\252&lTL\325\031jaz\215\232\243g\246\023M\3159I\251\001\247\255H\r.\352p$\324\203#\255J\204S\214\224\345\222\245\022\003\326\234\007\245<T\252i\370\364\251\025\252u5*\232\225jQR\001O\002\236\0058\n\\R\342\214R\021M\"\232V\232E0\2554\255t4\240R\321E!4\224\231\244\246\223McU\334\345\252&\250X\324\016y\250\234\325w5\003\032\205\215D\306\242f\250]\252\273\265B\306\242cQ\026\246\223Q\226\246\023M\335\353H95*\2169\247\n\220t\251\025sO\333\266\233\346`\361O\014M.\354Q\27352\014\324\350\265a\022\244\333\353J\023\322\237\202)\313\326\247Z\235*u\251\024T\240S\300\247\201N\002\234\005.(\333F\332iZiZiZa\024\322\265\277E\024\334\321Hh&\222\220\232i\2461\252\354y\250\330\324,j\274\206\241sP5B\365\003\324Lj\0275\003\232\201\215D\306\241cQ\023M&\230MFM&iG\006\246S\232pZ\225H\305L\224\366\\\216*\002\2304\340\330\244\335\232\221Fj\304B\256\304\231\251\200\305<.iB\363R\252\344sM)\203R\240\342\246Z\235*e\025*\212\220\nx\024\340)\300S\202\322\355\244\333HV\232V\232V\230V\230V\266\350\240\323h\246\232)3Hi\244\324NqP1\344\324lj&\252\362T-P\265D\302\241aP\270\250\034Uw\025\003T\017Q1\250\311\246\032a\024\302(\301\245\305J\275*E\346\236\006\rJ\247\025(a\212\211\332\241\316M=x\251\343\346\254\307\305\\\215\300\025 l\232\260\203\212SOC\232\221\223\214\322(\305L\242\246AV\024T\252*@\264\360\264\360\264\360\264\340\264\273i\n\322m\246\225\246\225\246\025\246\025\255|RQHz\322\032m!\244\2444\302j\'<T\016pj65\023\032\205\352&\250Z\243aQ0\250\\Uw\025\003\212\256\342\253\270\250\231j\"\264\335\264\205i\002d\323\304x\245\362\351v\323\325qO\333K\332\230X\212o&\234\006)GZ\265\020\251~\225b%&\255\"\342\246\007\002\234\243uXH\352m\234Sv\324\212\265*\n\260\202\246U\251B\323\302\323\302\323\302\323\266\321\266\215\264\233i\245i\245i\205i\205kO\024\204RR\032Jm!\244\246\232a\250^\241z\214\324MQ\265B\325\033TMQ\232\205\205@\342\240qU\335*&J\215\2435\023Di\236Y\357Hc4\2011N\000\367\245\002\224\nz\2558\256ivf\223\312\2441\342\230i\312\271\2531-YT\315Z\215@\251@\251V2j\314Q\342\255\004\000P\027\232\177\223\306E*\307O\013\212\231\026\254*\324\241i\341i\341i\341iv\322\355\243m&\332B\264\322\265\031ZiZ\277M\2444\230\246\221Hi\246\222\230i\215Q=B\342\242j\214\324mQ5F\325\023\n\215\205D\374\032\205\306j\022\264\206,\216j3\020\035\005D\321f\243x\261P\025\305&\332B\224\233iB\322\204\247\205\247\205\245\002\206\342\241v\250\361\223S\306\265:\220*d5e\rX\214d\325\330\323\212\231V\245U\342\227o5<c\326\234W\024\201jdZ\260\253R\205\247\205\247\205\247\205\247m\243m\033i6\322m\246\225\246\025\246\025\253dRSM6\220\322SH\246\323Z\243j\215\273\324M\322\242aQ\021Q\260\250\330TL)\214*&\025\013\324{\t\240\246)\205i\276]4\245E\"qUY2i\2451M\305&\332]\264\241i\301i\333i\002\320W\212\201\322\232\253S\242\324\351\0215f8qRc\025<\0035\241\030\300\251\325sS\250\247\204\315H\251N\307\024\212\2715aW\0252\255J\253O\013O\013O\013N\013F\3326\322m\244\333HV\230V\243e\251\373\320E0\212B)\010\246\221M\"\232E4\212c\n\215\205B\325\021\250\330TdS\010\250\330TMQ0\250\266n5(@\242\241q\223L\331K\266\242q\212\201\305W\"\243aL\331K\266\227m(Z~\332\002\323\212TL1Q\260\315\013\0375f8\352\322\000*a\216\324\205sV-\327\006\264\020T\350*U\025:\255<\n\220G\271iV<S\302\346\246E\251@\247\252\323\302\323\302\323\266\321\266\215\264\233i6\322\025\246\025\250\312\324\204z\322\032i\244\246\342\220\212n)\244SM1\252&\025\013\n\215\205FEFE0\212\215\252&\025\031\024*c\232k\nn\314\320S\024\302*\027\025\003\255B\313Q\225\244\333I\266\234\022\227e<&i\342*y\217\212\201\342\246\010\252E\212\246T\247\2044\365CR\355\251#\030\253h\3252\275Z\213\232\265\032\346\246\020\232\231#\300\240\2474\241qR\001R*\346\245\013R\005\247\005\247b\227m&\3326\322m\246\225\246\025\2462\320E4\212a\024\224Si\244b\220\361Q\232cTl*6\025\023\n\214\212\214\212c\n\210\212\215\205\"\246\346\251\0311\232\204\2574b\230\302\243e\250Yj&Z\211\226\243)I\345\322\354\245\tR\2549\251\026\034S\366`S\010\244\362\363M1\342\224%H\221\022j\312C\221R\255\267\265I\366ojQm\216\324\361\021\251\022#\232\275\024\\\n\273\004\\\325\321\030\305/\227Ha$dTe1NU\251\225jP\264\365ZxZv\337Z]\264\233h\333HV\232V\232V\243+Qb\220\323H\244\246\323i\r4\323M0\212\215\205F\302\242aQ\260\246\021Q\260\250\310\246\025\247F\270\247\025\250\231)\230\2468\250\215F\302\243+\232\215\222\233\266\223m(\214\232zE\315ZH\270\240\250\024\322*\"\274\323\325i\nd\324\211\025N\221b\254\307\035YH\200\251\2260jO \021\322\233\366nzT\211m\203\322\255$X\025*\361S\246MJ\005H\203\"\242t\346\220%J\253R\252\324\201i\341iB\321\266\215\264m\244+M+L+L+U\351\010\246\221M\"\232i\246\232i\206\233F)\214*&\025\031\024\306\025\031Z\215\226\243\"\2435\"/\002\244\333Q\262\324%i\214\265\036\312\215\222\231\262\220\2453\313\245\021T\213\020\251\004x\247\355\2462\324dSv\344\324\251\036jA\016jd\207\0252\303S\244X\251B\032\22649\253h\234S\304U:CNh\261Dp\222j\332\333\340t\250\331pjh\327\212\215\226\220/5\"\255L\253R\005\245\013K\266\227m\033h\333M+M+M+L+T\361F)\244SH\246\021M\"\223\024\326Zn)\010\2460\250\310\2460\250\312\323Yx\315D\302\243e\250\314D\236\265(B)\300R2\361P\025\250\331j<Rl\315!JB\224\337.\234#\245\013\212~))\215Q\021\232UJ\263\032U\224\2175*\305Vc\213\332\246\021S\2045*E\212\235R\245H\352\312G\201N\362\362jx\240\002\244u\300\252\254\274\323\324`R\025\246\355\346\244U\251\225j@\264\241iv\321\266\227m&\332B\264\322\264\302\264\302\265G\024b\220\212i\024\302)\244SqHE0\322c\212a\024\302*2)\245i\2733Q\025\244\t\223La\264\342\244\333\3057o42\361P\225\246\024\315\'\223I\263\024\306Z\214\212P)M%!\246Q\214\322\210\352E\212\254G\035ZH\352\314qf\255$5*\303O\021R\210\361R\254u2&*`\265\"\'5aW\002\230\353\232\204\245.\332M\264l\247\252\324\212\265 Zv\3326\322\355\243m!ZB\264\322\264\302)\205k?\024b\220\323H\246\021M\"\233\212\010\250\310\244\3054\212\214\2557\024\322)\204SJ\323v\323\014yjy\030\030\246\201JW\212\211\226\230\026\203\214TLj6\250\361F1H\0015 N)\254\265\021\030\245Q\232\260\221\346\254$5:\307\212\235#\315[\212:\270\221\324\242:]\224\361\030\247*T\241)\352\2252GRc\024\326Zf\312M\224l\243m8-=V\236\005;\024\273h\305\030\244\333HV\232V\230E0\255f\342\222\220\212i\031\244\"\230E7\024\204S\010\243\024\302)\245j2)1HV\233\266\233\266\225S\223H\313\3157m\005j6Zc-@\347\025\021\250\230\322\016i\304S\220\n\221\230\001P3f\230FjX\243\315\\\216<U\224Z\231S5f8\252\334q\212\262\211S\254|S\035v\322)\251\000\251TT\250\234\325\205N)\n\342\220\255&\332n\332\002\363K\262\227e(Z\220\n]\264\270\243m.\332B\264\322\264\322\264\302)\214\265\225\2121I\212LSH\246\221M\"\223\024\322\264\335\264\204S\010\246\025\244\333I\266\223m7o5 L\niJn\312\nT.\2705\033t\252\3169\250XTei1K\214\323\225M)\214\232A\001\251\004\0252E\212\262\211R\205\305K\020\346\256 \342\254GVR\246\007\002\243\220f\242PA\253(3S\252\324\310\265:\216(\"\231\266\214S@\245T\251\004y\247\210\263M1b\232\026\234\0058-.\3326\322\025\244+L+L\"\243aYX\346\223\024\230\244\"\223\024\322)\245i6\322b\232V\232V\232V\233\266\215\264l\246\224\244\331\315H\023\"\232\311HR\233\266\232\321f\252J\230\252\314*\026Zi\025\0369\251\2213R\252\n\235c\024\024\024\004\024\270\305I\035ZT\310\247*`\325\210\305[\215ju\247\n~\320EF\311\203O\214\342\254\241\251T\324\300\321\232\\f\232\374p)\025x\247\252\340\324\312\265(Z\0313P\262`\322\005\311\245\000\347\024\360\264m\244\333M+L+L+Q\262\326F(\305&)1HE!\024\233i\245i\245i\010\246\225\246\225\243m\033)Lt\335\224\276VjA\036\0055\243\246\224\250\366sJW\002\251N:\3257Z\201\205&\334\322yx4\365\030\251\024f\246Piv\223K\214R\355\315*\2575z\001\220*\307\227OH\361V\024b\236)\342\2363K\264\232@\270\251V\244S\315YS\221F)\340`SB\344\344\324\212\264\355\265*\212\224\n]\264\307L\212\213f)\3120y\247\355\346\215\264\205i\205i\205i\204Tl+\037\024b\223\024\230\366\244\305\033i6\322\025\246\225\246\225\244+I\262\200\224\355\224\273)\273*E\217\212pJC\036j6\212\231\345\342\243\227\201Y\362\214\232\254\353P\224\247*P\311L\3075*T\312E8\221\212i\346\225EJ\211\223W\240\217\025mR\244\013\212x\024\241jEL\324\311\0259\223\024\300\2315\"\305S,5*\304E=c\251V>\016j=\2704\365\024\375\264\345Z\231V\237\267\212M\264\315\234\323\n\342\244\333\362\203AZaZiZaZ\215\205F\302\261\261F)\270\243\024\230\245\305!\024\230\244\"\232V\223m\033iBS\202\322\354\245X\362jA\035;\313\243\313\250\335@\252\322\034UII5U\320\232\201\2434\317.\232W\024\021\221L\331\315<Fi\353\031\247\2244\251\021&\254,<T\311\026\rZE\000T\302\237R*\323\302T\350\2252\256)\031sO\216,\324\342*\262\221\002)\306,Rm\305!<`S6f\234\027\024\340)\300T\250*R\0061M\333J\0274yt\246<-0\212iZaZ\215\205F\302\243aX\300Q\212B)\247\212i4\224s\351Hr(\006\227\031\245\331J\026\234\022\227e8%H\261\324\201)\257\305B\317P;f\240u\315B\321\324m\025B\321\324f:\212H\361L\013\232<\263\232z\256*E\025(\214R\205\000\324\212EJ\274\324\312*E\025*\214\325\204Z\224-J\242\236\0058.jdZ\235W5b4\342\225\226\230\313P\343$\342\236\005.)B\322\343\024\365\342\2369\247\342\225EH\026\224\247\025\013/&\230E1\205F\302\242aQ\262\3268Z1Q\265\'\226Z\244X\t\352)\342\000)Lb\2436\340\3645\004\260\2249\024\211\351S*\346\237\262\224%8%8GS,t\245p*\274\2435]\226\241\220\005\025_\314\346\224\020z\320\312*\026Ni\236^j)b\342\253\252`\324\342,\212C\r\013\021\025(\030\024\335\244\323\225\rX\215\rN\026\244QS\"\325\205Z\225V\245T\251\002S\325*eJ\231R\254\"\361C\n\215\207\025\010\0304\354Q\212p\024\340\264\354S\220sR\020\0059EH\203&\236W\025\023\256~\265\t\025\033\n\211\205F\302\243aX\324c4\242.y\251\200P0\005\033I\351A\030\035j2i\t\305!\001\3075\031\267\364\247F9\301\353S\005\247\205\245\331\212@~lU\205Zd\202\2532\346\242\223\n*\224\274\325r\244\032L\322\026\"\243g4$\274\363S\262\007Z\254a\301\247\205\300\247*\344\324\242.)\246,R\010\375\252d\216\246\010\005;mH\261\346\247H\352\302\307\305=W\025:-H\022\235\214T\211S\245Y@1Lu\246\021\305BW\232\\R\355\245\003\024\360)H\342\235\030\317J~9\245\305H\215\203R1\365\2460\250\030sQ\260\250\330TL*6\025\214\027\361\251Q=i\342<\363I\267\007\245\014\307\030\250Y\252\"rh\353R\'\002\244\002\232@\317J\225E<\n\\S#\\\271\253J\274Tn*\007\030\025J^M@V\242t\250Yi\204TL\264\314\02552\271\3059>c\203S\371y\024\320\2305f5\004S\214Y\240AO\021b\236#\245\021\324\250\230\251\221j\302\247\024\204`\324\321\221R\323s\223S\306\001\024\376\2254oRu\246?J\213\024\001K\212\\P)\325,k\201Hz\323\2513\203S!\336>\224\036\265\033\016j&\025\023\n\215\205B\302\263\025*M\240\n:\212a5\033\232\205\2174\312z\322\223\216i\301\370\342\234\274\324\252\265(ZR\274S!\341\315Y\003\212\216A\200MRv\'5\013.j\"\265\023\255DV\243d\250\212\323\nf\200\270\247\257\006\256Dw\n$\\sI\023`\342\257F\273\205K\260\n<\274\323\204F\234\"5\"\305O\n\027\255J\035@\250$q\232X\344\251\374\314\212n\376j\304r\020)\373\362jd\247\356\244\334qL\357O\024\242\234\005.)@\251\001\3055\251i\010\247+\025\351S*\356\346\221\222\242d\342\240qP\260\250\330U\000\270\240\340Td\322\032\205\315B{\323i\340P\307\212E<U\204\025:\212~8\245\347\024\221\240\337\232\230\374\242\243s\221U\0359\246\024\250\331*&Z\210\255F\313P\262\3236\322\354\243mM\017\006\255\024\334\265\017\225\264\325\230\344\332*_754L;\325\215\353@\221hi@\351P<\271\351M\014\306\234\020\236\265*\307\212\220%<GR\252\201\326\234\000\365\251\024\343\245J\017\024\204\322\nx\247S\200\247b\227\024\264\032@9\305-8\n\236#\306)\314*\'\025Y\305B\302\242aT\rF\306\243cLg\342\241&\232i@\247\342\232\343\"\210\200<U\225\\qR\255<\032ZU\340\346\234[ \324f\230\302\242e\250\231j&Z\210\212c-D\311L+I\217j6\323\227\203\305Z\211\370\301\251\366\006\246\371T\341\035H\024\216\224\034\322\000\324\340\204\323\304t\274\npjQ!\025\"K\353R\356\310\310\245\334M9sS(\342\236)\373sJ\006)@\247\201N\002\234\005;\024b\227\024\322)@\247/\314i\377\000t\361O\316E1\371\025Y\352\026\250\232\263\230\324Lj\026j\214\234\323M&9\247\216\00586i\257\351P\243lz\274\247#5 jpjZp4\200\201\301\247\210\375i\256\230\250H\250\330Tl*&\025\021\024\302)\204Sv\321\266\227\024\345\342\246Y\rI\346R\211i\302Zp\2234\360\364\340\374Q\346\032n\354\321\232PsOZ\221IS\355S\251\317J\225EL\265 \024\340)\330\245\002\234\0058\nx\024\340(\305\024\204Q\212T8jRrh\017\212Fj\201\315@\324\306\254\206j\211\232\242c\232BqM\242\236)qM5\024\243\034\323\240\270\354j\322I\232\2247\255;9\247\nF\007\250\247\307q\3742\017\306\236\314\276\265\001\031\246\260\250\231j&Z\215\226\243+M+L\"\214Q\2121N\024\360)v\323\200\247\201R\001O\035(\002\214S\266\322\205\247\201R\001OPGJ\235:T\353R\250\247\201N\002\234\026\234\026\234\026\234\026\234\005.\3326SJ\321\212n(5\031\342\230Z\230y\250\3150\326\031j\215\2156\220\232J3J\r8\032By\250\2449\025\014C\347\253\253\362\324\352jA\355N\006\235\232f\321\232v\320p{\322\201\305#.FEF\313Q\262\324L\265\031Za\024\302\264\334R\342\214R\201O\002\234\005=P\323\302\323\200\247\250\247\205\030\240\256)@\247\001N\002\244\002\236\005H\243\0252\324\313R-<\nx\024\340\264\340\264\354T\2423\212i\\Q\212n)\n\323J\323H\250\315F\302\243\246\032i\256|\232a4\332BqH[\024\302\364\241\351\333\250\r\232k\232\205N\032\255+\344T\361\265J\017\024\360iA\247u\245\035)\312)\330\3151\226\243e\364\250\231j&Z\214\2550\212n)1K\212P)\300S\224T\352)\373h\333J\005<\n~(\331J\026\236\0058\n\221EH\005H\242\245Z\221jP*@)\340T\201iv\340\212pbh#\024\230\244\"\232E4\212k\n\211\205D\302\243\"\230i\206\271\322i\244\322TO%D_4\205\351\003\342\235\346P$\346\234[5\033t\342\235n\371m\254j\370\030\251\025\251\340\323\3058S\324S\205<R5V\226P\247\024\335\331\355HT\032\215\226\242+M\333M\333K\266\227\024\001N\025\"\234T\201\251\302\234\005H\253O\013N\333K\266\224\nx\025\"\212x\024\345\251\205H\242\246QR(\251Uj@\270\245\003\232VQ\214\212i\311\034\322Rb\223\024\204S\010\250\331j\026Z\211\205FEFk\234\244<T.\335\205Ws\3150\2750\275&\372\013R\253\363\315J\257RpED\312U\262\265b+\223\300z\270\2370\342\245\034S\325\252AO\034\323\305>\232\325\237u\301\247\301\312\214\324\333\007jk!\250\231)\205)6\322m\244\333F\3321N\247\212\221MH*E\251\005-8R\201N\003\322\234*AR(\251\005H\2652\324\350*eZ~\332\220\240\t\232\217\024\302)1F)\r!\024\322)\214*\027\025\013-D\302\242a\\\331\353Lv\300\252\254\325\0136j3M&\233\2327S\263R+T\310sO\333L+\203\232\225.\031:U\313k\201\'\r\326\257\252\251\035\205\007`\007\232\024\203\322\244\006\224\265C$\241G\275R\226O9\300\035\005Y\215p\005J)OJaZaZaZiZM\264m\244\305.)@\247\212x5\"\232\22058\032vi\302\236:S\2058T\253R\255J\242\245Z\235\rN\206\245\003=($\3644\303L4\224Q\214\323\274\2763Q\021Lj\211\205B\302\242aP\260\256d\232\2573UGja4\302i\204\323I\244\31585H\246\246\215\271\253jF)\255\3157fjH\243 \344U\261+\001\201Mb\344pi\251<\221q\326\245K\341\2347\024\367\277\215W\206\346\251\231\344\270l(\300\253pC\264s\326\254\201\212Z\0014\204\2323\352(\3004\322\264\233(\331I\345\321\262\215\224\270\245\002\226\236\016)\352i\342\245QR\001N\013\351O\002\236\0075\"\324\212je5*\232\235MH\255\212R\324\302i\231\346\226\212T\344\325\223\235\234UV\0305\031\025\023\n\205\205D\325\013\n\345\\\340U)\033&\240&\230M4\234\323M4\322P*E\251\220\324\353%.\374\232\225Njej\225\006j\\\niAPI\000jbY\202y\253\321@\2508\025>1K\364\240\nZ1\232]\264\340\202\227`\240\306)\273\005&\3126\322l\3154\255\030\305\030\315.)\352*U\025*\212\225V\244QO\013\232v\302)\3123O\003\024\365\251T\324\252\325 j\\\322SM(4\3602)\007\312r*_;#\232\205\216M0\323\030T\016*\026\025\013\n\343fz\250\315\232\214\232a4\224\206\232i)@\247\003OSR\203J\t\006\247F\251\224\214\325\204z\225H\247\022(\306i\350\2650\030\034R\321\214\322\205\247b\214R\201N\002\234\005.)\010\244\305&\3326\322\024\246\354>\224m=\305.\332r\255H\253R\250\251TT\252\265*\257\2558\014PW\373\264\003\353O\003\322\234)\340\323\324\323\203R\203A\244\006\244CO#5\031\244=i\244S\032\242a\232\205\205D\302\270)\0375\t4\302i\264R\023M4\224\023@4\3655\"\265J\246\244Z\231ML\206\246\006\236\274\324\300qO\035)\324\352v)@\247b\224\np\024\270\247\001K\212\000\245\332)\245Gj\002\203K\264\nLz\322\021\352(\013N\tO\013R*\324\252\265\"\373S\305;u\031\346\203\203\365\241\01685(\347\2458S\300\245\242\235\326\234\200g\2321\203\305;4\322i3Hi\206\243j\211\205B\302\274\355\215FM4\232m\031\244\315!4\334\321@\247\212z\232\231\rL\265(\036\225\"\261\251\326\246Z\220\032x\247\212p\024\361N\024\340)\300S\261@\024\354R\342\212Bh\244\2434\023FE\002\244\002\236\277J\220})\340\372\323\263F\352\\\322\346\224\032p9\353N\\\212\231H>\306\236\005?\024\252\007~izg\024\001A\024\224\206\222\220\232i\246\032\215\207\245D\302\274\321\2150\232i4\231\367\244\315!ji4\231\245\006\234)\342\236\265*\n\235je\251\224T\252*E\251\224T\200S\200\247\201O\002\236\005.)pi\324\240R\321Hi)3F\352JL\322\322\203N\r\212\221\\T\201\251wR\203N\315(4\271\245\315(jz\265J\0105*\277@\177:\224\032x\366\245\002\220\320y\024\322\010\246\322u\244\244\"\230E4\212k.k\313\t\246\223M&\232M!4\231\244\242\234\0058T\200T\212*e\0252\n\231EL\242\246QR\001R(\251\005<\nx\024\360)\340R\212ZZ3Fi3Fi\t\246\223I\2328\307\275\007\2123\212\001\247\003\232^\247\212\220\034S\303S\203S\201\247Q\232\\\321\232p8\247\253\324\253&jU\223\037J\225^\244\017\232RE&\352BsM4\235\250\372\323M\030\246\342\232Ey94\204\323i\264\332\r\024\240S\300\247\201R(\251\024T\310\265:-L\242\245QR\250\251\024T\252*@)\340S\300\247\001O\002\227\024b\203HM4\232L\322\023I\232L\321\272\214\323\272\320\t\034v\241\207qJ\275)\303\245<\021J\r<S\201\247R\216\264\264R\346\200i\301\252E\222\245W\251\003\323\304\236\264\273\2517Q\272\214\363\305\005\251\013Rn\244$SI\025\344\331\244\244&\232M%\024\240S\300\247\001R\001R(\251\225jeZ\225EL\242\245QR(\251TT\212*@*@)\340S\261K\214t\245\006\203M&\232i\244\322f\233\232\t\244\2434\231\247\003N\004\366\247\014\343\236iW\234\346\227\214P)\300\323\305<S\2058R\321E\024S\263N\r\212\221^\236\036\224=.\3727R\357\245\335I\272\232M!jaj\362\274\322\023M&\222\212P)\300S\300\247\201R(\251\024T\352*U\025*\212\225EJ\242\245QR\250\251\000\247\201O\002\236)sE\031\246\2264\026\3154\232L\323sIE!4\206\200sJ\r=i\331\305\024\2434\341N\247\003O\006\236\r8\032Z)GJR(\244\"\200is\212P\364\355\364\273\350\337J\036\227u\033\350\335A4\302k\313(\'\024\332)E-<\nx\024\360*E\0252\255L\242\245QR\250\251\024T\312*U\025\"\324\202\236\005>\2274\231\245\315&i3IHE4\212L\322f\215\302\223\"\223\255\030\305/\326\224\023N\025 \245\247R\212u8\032p4\340isN\024\242\224\n\\PE2\227&\214PM&qF\352\003R\357\240\275 zx|\322\023_\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\003,IDATx^\355\335\335r\263 \020\000\320\214\357\377\314\231\357\247^\264:QQ(\260{\316]\2155\215\254\013,\306\276^\324\360\336n\000\000\000\000\242R\006\000\000\200r\306\321P`\331n\210@\026\270+d8\000\000\000\000\300\240\324\343\000\000fb\035\032\000\000\000 \253\307\353zJK\220\316\343\274\001\000\000\000\000@\037\n\274@\t9#\261\326\215\357F\003\000\000\000\"2\337\375\311\371\000\230C\353z0\000\320\231\311\331=\261\007I\261?\035\000\000\000\337\230\002\002\000\000@\036ow\010\000@J\326\002 )\343\377\334\264?\000\000\000\300\324\324\366\001\000\000 0\367u\000\000\000\000\000\000\000\300)\313\353y\371N\00506Y*=!\000\000\211x\252?\000\000\000\307L\034\271\302\322\002\000\000\000\000\000\000\000\000\000\000\000\304\347\373\003\000\000\000\000\000\323Q\332\005\000\200\346\026\343n\000\000\000\000\000\000\000\306gu\033\000\000\000\000\000\000\000\000\350\316\215L\000\000$b\370\233\331{\273\001\000\000\000\000\000\022\361\317|\000V\026\0169\262\215\217\355\317\3718\003\000\000\000\000\000\000\201Y\014\312\300MC\214\357N\224\272\037\016\000\000\000Z0\341\006\000\000\000\202Q\356\000\000\000\000\210F\305\007\000\000(\342\271\032\344 \322\001\000\000B3\355\003\370D\206\004\000\200\021\271\343\233j\004\023\3004\244l\000\000\332\263>\014\000\000\220K\257\325\207\306\357kz\013<\3208C\001\000\300\2573\306\005\000\000\340\216O\363\311\375r\354\317-\333\337\333\357?\276\345\257\355\266i\254\'\374\376\007(j\261\363\2679\337\003\310K\206\000\350\257h\360\307\320\264e \006I1]\276Hk\004@\215cp\317\345\206\006\000\000\000xB\021\002\200\036\364?\311\t\000\340\036\353\327\305$\\\000f\243\273\007\000\010\242h`W\2643\000\014M\257\266\223k\251B\000\000_r\345\276\007\244\315\272v\201\267\333P\231\006\344\221\377\001\324:J\031\334[\004\260\022\n\311\t\000\200tL\'!%\227>\000[\372\006 \275\2654\232\257B\252\007\000\000\000\000\000RQ\024\005HNG\000\337\344[ \377]\303\236\337\257?\314\327I\233\321\325\000\000\00000\005\201\030\224\037\000\370Hg\237\233\366\207C\023_\"\2139\000\'&\016o\036\221\034r8\270\302\017^b<\325\233\253\372\001\211L\2171\213\253\027\266\026\315\355j\234\000\323\223\356os\352\206\246#\003\000\200r\2469\311M\033\000\255g\200N\0140\245i\223\027Uh\177`X\022TX\0361GWM\002\260\3051y\350\270\0279~\025\200n\364\251\000\3008L\035\353i5\312ku\\xI\001\2759\377\364!\362\206\241\217ON\000\000\000P\235\t\037+\217\320\316N\000d\367/\002N\352\016\313\351\036\244 \n !\027>@R:\000\000\200F\024\3449d$\016\000\361U\350\357+\034\002\200N\344p\000\000\000\000\000\000\000\000\000\000\000\006\362\007Wv]\t\272\351Z\n\000\000\000\000IEND\256B`\202"
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 7c27b96..702e97f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -28,8 +28,7 @@
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"Ujumbe wa sauti"</string>
<string name="defaultMsisdnAlphaTag" msgid="2285034592902077488">"MSISDN1"</string>
<string name="mmiError" msgid="2862759606579822246">"Tatizo la muunganisho au msimbo batili MMI."</string>
- <!-- no translation found for mmiErrorNotSupported (5001803469335286099) -->
- <skip />
+ <string name="mmiErrorNotSupported" msgid="5001803469335286099">"Kipengele hakitumiki."</string>
<string name="mmiFdnError" msgid="3975490266767565852">"Ni matumizi yanayohusisha nambari za simu zilizobainishwa pekee yatakayowezekana."</string>
<string name="mmiErrorWhileRoaming" msgid="1204173664713870114">"Haiwezi kubadilisha mipangilio ya kusambaza simu kutoka kwenye simu yako ukiwa unatumia mitandao mingine."</string>
<string name="serviceEnabled" msgid="7549025003394765639">"Huduma iliwezeshwa"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 6434c2c..528575b 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1194,8 +1194,8 @@
<string name="no" msgid="5122037903299899715">"Скасувати"</string>
<string name="dialog_alert_title" msgid="651856561974090712">"Увага"</string>
<string name="loading" msgid="3138021523725055037">"Завантаження..."</string>
- <string name="capital_on" msgid="2770685323900821829">"УВІМК"</string>
- <string name="capital_off" msgid="7443704171014626777">"ВИМК"</string>
+ <string name="capital_on" msgid="2770685323900821829">"УВІМКНЕНО"</string>
+ <string name="capital_off" msgid="7443704171014626777">"ВИМКНЕНО"</string>
<string name="checked" msgid="9179896827054513119">"вибрано"</string>
<string name="not_checked" msgid="7972320087569023342">"не вибрано"</string>
<string name="selected" msgid="6614607926197755875">"вибрано"</string>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index abbff58..c8a65a7 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1709,6 +1709,13 @@
for more details.
-->
<flag name="shortService" value="0x800" />
+ <!-- The file management use case which manages files/directories, often involving file I/O
+ across the file system.
+ <p>Requires the app to hold the permission
+ {@link android.Manifest.permission#FOREGROUND_SERVICE_FILE_MANAGEMENT} in order to use
+ this type.
+ -->
+ <flag name="fileManagement" value="0x1000" />
<!-- 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
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2a16d3..d40adf5 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -118,6 +118,11 @@
<bool name="config_using_subscription_manager_service">false</bool>
<java-symbol type="bool" name="config_using_subscription_manager_service" />
+ <!-- Whether asynchronously update the subscription database or not. Async mode increases
+ the performance, but sync mode reduces the chance of database/cache out-of-sync. -->
+ <bool name="config_subscription_database_async_update">true</bool>
+ <java-symbol type="bool" name="config_subscription_database_async_update" />
+
<!-- Boolean indicating whether the emergency numbers for a country, sourced from modem/config,
should be ignored if that country is 'locked' (i.e. ignore_modem_config set to true) in
Android Emergency DB. If this value is true, emergency numbers for a country, sourced from
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d0372ea..39012ae 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1201,6 +1201,11 @@
<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_foregroundServiceFileManagement">run foreground service with the type \"fileManagement\"</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_foregroundServiceFileManagement">Allows the app to make use of foreground services with the type \"fileManagement\"</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>
@@ -6402,4 +6407,22 @@
<!-- Display content to tell the user the sim card name and number-->
<string name="default_card_name">CARD <xliff:g id="cardNumber" example="1">%d</xliff:g></string>
+
+ <!-- Strings for CompanionDeviceManager -->
+ <!-- Title of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+ <string name="permlab_companionProfileWatch">Companion Watch profile permission to manage watches</string>
+ <!-- Description of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_companionProfileWatch">Allows a companion app to manage watches.</string>
+ <!-- Title of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+ <string name="permlab_observeCompanionDevicePresence">Observe companion device presence</string>
+ <!-- Description of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_observeCompanionDevicePresence">Allows a companion app to observe companion device presence when the devices are nearby or far-away.</string>
+ <!-- Title of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+ <string name="permlab_deliverCompanionMessages">Deliver companion messages</string>
+ <!-- Description of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+ <string name="permdesc_deliverCompanionMessages">Allows a companion app to deliver companion messages to other devices.</string>
+ <!-- Title of start foreground services from background permission [CHAR LIMIT=NONE] -->
+ <string name="permlab_startForegroundServicesFromBackground">Start foreground services from background</string>
+ <!-- Description of start foreground services from background permission [CHAR LIMIT=NONE] -->
+ <string name="permdesc_startForegroundServicesFromBackground">Allows a companion app to start foreground services from background.</string>
</resources>
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index d1d14f6..44923b6 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -117,7 +117,8 @@
public void testSendHint() {
Session s = createSession();
assumeNotNull(s);
- s.sendHint(Session.CPU_LOAD_UP);
+ s.sendHint(Session.CPU_LOAD_RESET);
+ // ensure we can also send within the rate limit without exception
s.sendHint(Session.CPU_LOAD_RESET);
}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 352c6a7..aa1853f 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -857,7 +857,10 @@
ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_DELETE_CONFIG,
+ compositeName,
+ null);
assertThat(result).isNotNull();
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index ee0b127..2e31bb5 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -76,7 +76,7 @@
when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
mMockContentResolver = new MockContentResolver(InstrumentationRegistry
.getInstrumentation().getContext());
- mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(),
+ mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
mMockContentProvider);
mCacheGenerationStore = new MemoryIntArray(1);
mStorage = new HashMap<>();
@@ -84,7 +84,7 @@
// Stores keyValues for a given prefix and increments the generation. (Note that this
// increments the generation no matter what, it doesn't pay attention to if anything
// actually changed).
- when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+ when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
@@ -104,7 +104,7 @@
// Returns the keyValues corresponding to a namespace, or an empty map if the namespace
// doesn't have anything stored for it. Returns the generation key if the caller asked
// for one.
- when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+ when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_LIST_CONFIG),
any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
Bundle incomingBundle = invocationOnMock.getArgument(4);
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 4adbc91..1331779 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -345,27 +345,33 @@
try {
// value is empty
Bundle results =
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
// save value
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_PUT_CONFIG, name, args);
assertNull(results);
// value is no longer empty
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// save new value
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_PUT_CONFIG, name, args);
// new value is returned
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -379,23 +385,25 @@
try {
// save value
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
// get value
Bundle results =
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// delete value
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+ results = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
null);
// value is empty now
- results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -413,12 +421,12 @@
try {
// save both values
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
// list all values
- Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+ Bundle result = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
null, null);
Map<String, String> keyValueMap =
(HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -428,14 +436,15 @@
// list values for prefix
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
- result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+ result = r.call(Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_LIST_CONFIG, null, args);
keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
assertThat(keyValueMap, aMapWithSize(1));
assertEquals(value, keyValueMap.get(name));
} finally {
// clean up
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
- r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
}
}
}
diff --git a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
deleted file mode 100644
index 5664e0b..0000000
--- a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
+++ /dev/null
@@ -1,121 +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 android.view;
-
-import static android.view.InsetsState.FIRST_TYPE;
-import static android.view.InsetsState.LAST_TYPE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link InsetsVisibilities}.
- *
- * <p>Build/Install/Run:
- * atest FrameworksCoreTests:InsetsVisibilities
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class InsetsVisibilitiesTest {
-
- @Test
- public void testEquals() {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- final InsetsVisibilities v2 = new InsetsVisibilities();
- final InsetsVisibilities v3 = new InsetsVisibilities();
- assertEquals(v1, v2);
- assertEquals(v1, v3);
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- v1.setVisibility(type, false);
- v2.setVisibility(type, false);
- }
- assertEquals(v1, v2);
- assertNotEquals(v1, v3);
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- v1.setVisibility(type, true);
- v2.setVisibility(type, true);
- }
- assertEquals(v1, v2);
- assertNotEquals(v1, v3);
- }
-
- @Test
- public void testSet() {
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- final InsetsVisibilities v2 = new InsetsVisibilities();
-
- v1.setVisibility(type, true);
- assertNotEquals(v1, v2);
-
- v2.set(v1);
- assertEquals(v1, v2);
-
- v2.setVisibility(type, false);
- assertNotEquals(v1, v2);
-
- v1.set(v2);
- assertEquals(v1, v2);
- }
- }
-
- @Test
- public void testCopyConstructor() {
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- v1.setVisibility(type, true);
- final InsetsVisibilities v2 = new InsetsVisibilities(v1);
- assertEquals(v1, v2);
-
- v2.setVisibility(type, false);
- assertNotEquals(v1, v2);
- }
- }
-
- @Test
- public void testGetterAndSetter() {
- final InsetsVisibilities v1 = new InsetsVisibilities();
- final InsetsVisibilities v2 = new InsetsVisibilities();
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- assertEquals(InsetsState.getDefaultVisibility(type), v1.getVisibility(type));
- }
-
- for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
- v1.setVisibility(type, true);
- assertTrue(v1.getVisibility(type));
-
- v2.setVisibility(type, false);
- assertFalse(v2.getVisibility(type));
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
similarity index 66%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
rename to core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
index 004df2a2..281d677 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/core/tests/coretests/src/android/window/SnapshotDrawerUtilsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 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.
@@ -14,22 +14,20 @@
* limitations under the License.
*/
-package com.android.wm.shell.startingsurface;
+package android.window;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-
import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.eq;
+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.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.TaskDescription;
import android.content.ComponentName;
@@ -42,33 +40,28 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowInsets;
-import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestShellExecutor;
-
import org.junit.Test;
import org.junit.runner.RunWith;
/**
- * Test class for {@link TaskSnapshotWindow}.
- *
+ * Test class for {@link SnapshotDrawerUtils}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotWindowTest extends ShellTestCase {
+public class SnapshotDrawerUtilsTest {
- private TaskSnapshotWindow mWindow;
+ private SnapshotDrawerUtils.SnapshotSurface mSnapshotSurface;
private void setupSurface(int width, int height) {
- setupSurface(width, height, new Rect(), 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(width, height, new Rect(), FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, width, height));
}
- private void setupSurface(int width, int height, Rect contentInsets, int sysuiVis,
+ private void setupSurface(int width, int height, Rect contentInsets,
int windowFlags, Rect taskBounds) {
// Previously when constructing TaskSnapshots for this test, scale was 1.0f, so to mimic
// this behavior set the taskSize to be the same as the taskBounds width and height. The
@@ -80,12 +73,13 @@
Point taskSize = new Point(taskBounds.width(), taskBounds.height());
final TaskSnapshot snapshot = createTaskSnapshot(width, height, taskSize, contentInsets);
- mWindow = new TaskSnapshotWindow(new SurfaceControl(), snapshot, "Test",
- createTaskDescription(Color.WHITE, Color.RED, Color.BLUE),
- 0 /* appearance */, windowFlags /* windowFlags */, 0 /* privateWindowFlags */,
- taskBounds, ORIENTATION_PORTRAIT, ACTIVITY_TYPE_STANDARD,
- WindowInsets.Type.defaultVisible(), null /* clearWindow */,
- new TestShellExecutor());
+ TaskDescription taskDescription = createTaskDescription(Color.WHITE,
+ Color.RED, Color.BLUE);
+
+ mSnapshotSurface = new SnapshotDrawerUtils.SnapshotSurface(
+ new SurfaceControl(), snapshot, "Test", taskBounds);
+ mSnapshotSurface.initiateSystemBarPainter(windowFlags, 0, 0,
+ taskDescription, WindowInsets.Type.defaultVisible());
}
private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
@@ -101,8 +95,8 @@
0 /* systemUiVisibility */, false /* isTranslucent */, false /* hasImeSurface */);
}
- private static TaskDescription createTaskDescription(int background, int statusBar,
- int navigationBar) {
+ private static TaskDescription createTaskDescription(int background,
+ int statusBar, int navigationBar) {
final TaskDescription td = new TaskDescription();
td.setBackgroundColor(background);
td.setStatusBarColor(statusBar);
@@ -116,7 +110,7 @@
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(200);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 200));
verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
}
@@ -126,7 +120,7 @@
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(200);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 100));
verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(100.0f), eq(200.0f), any());
}
@@ -136,7 +130,7 @@
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(200);
when(mockCanvas.getHeight()).thenReturn(200);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
verify(mockCanvas).drawRect(eq(100.0f), eq(0.0f), eq(200.0f), eq(100.0f), any());
verify(mockCanvas).drawRect(eq(0.0f), eq(100.0f), eq(200.0f), eq(200.0f), any());
}
@@ -147,7 +141,7 @@
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 100, 100));
verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
}
@@ -157,76 +151,76 @@
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
+ mSnapshotSurface.drawBackgroundAndBars(mockCanvas, new Rect(0, 0, 200, 200));
verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
}
@Test
public void testCalculateSnapshotCrop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 100, 90), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(0, 0, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_taskNotOnTop() {
- setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, 0, new Rect(0, 50, 100, 150));
- assertEquals(new Rect(0, 10, 100, 90), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(0, 10, 0, 10), 0, new Rect(0, 50, 100, 150));
+ assertEquals(new Rect(0, 10, 100, 90), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_navBarLeft() {
- setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(10, 0, 100, 100), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(10, 10, 0, 0), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(10, 0, 100, 100), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_navBarRight() {
- setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(0, 0, 90, 100), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(0, 10, 10, 0), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(0, 0, 90, 100), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotCrop_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
- assertEquals(new Rect(5, 0, 95, 90), mWindow.calculateSnapshotCrop());
+ setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
+ assertEquals(new Rect(5, 0, 95, 90), mSnapshotSurface.calculateSnapshotCrop());
}
@Test
public void testCalculateSnapshotFrame() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 0, 10);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
assertEquals(new Rect(0, 0, 100, 80),
- mWindow.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
+ mSnapshotSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90)));
}
@Test
public void testCalculateSnapshotFrame_navBarLeft() {
setupSurface(100, 100);
final Rect insets = new Rect(10, 10, 0, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
assertEquals(new Rect(10, 0, 100, 90),
- mWindow.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
+ mSnapshotSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100)));
}
@Test
public void testCalculateSnapshotFrame_waterfall() {
- setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100));
+ setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, new Rect(0, 0, 100, 100));
final Rect insets = new Rect(0, 10, 0, 10);
- mWindow.setFrames(new Rect(5, 0, 95, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(5, 0, 95, 100), insets);
assertEquals(new Rect(0, 0, 90, 90),
- mWindow.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
+ mSnapshotSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90)));
}
@Test
public void testDrawStatusBarBackground() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
+ mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 50, 100));
verify(mockCanvas).drawRect(eq(50.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
}
@@ -234,11 +228,11 @@
public void testDrawStatusBarBackground_nullFrame() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, null);
+ mSnapshotSurface.drawStatusBarBackground(mockCanvas, null);
verify(mockCanvas).drawRect(eq(0.0f), eq(0.0f), eq(90.0f), eq(10.0f), any());
}
@@ -246,50 +240,50 @@
public void testDrawStatusBarBackground_nope() {
setupSurface(100, 100);
final Rect insets = new Rect(0, 10, 10, 0);
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
+ mSnapshotSurface.drawStatusBarBackground(mockCanvas, new Rect(0, 0, 100, 100));
verify(mockCanvas, never()).drawRect(anyInt(), anyInt(), anyInt(), anyInt(), any());
}
@Test
public void testDrawNavigationBarBackground() {
final Rect insets = new Rect(0, 10, 0, 10);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
+ mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
verify(mockCanvas).drawRect(eq(new Rect(0, 90, 100, 100)), any());
}
@Test
public void testDrawNavigationBarBackground_left() {
final Rect insets = new Rect(10, 10, 0, 0);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
+ mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
verify(mockCanvas).drawRect(eq(new Rect(0, 0, 10, 100)), any());
}
@Test
public void testDrawNavigationBarBackground_right() {
final Rect insets = new Rect(0, 10, 10, 0);
- setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ setupSurface(100, 100, insets, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
new Rect(0, 0, 100, 100));
- mWindow.setFrames(new Rect(0, 0, 100, 100), insets);
+ mSnapshotSurface.setFrames(new Rect(0, 0, 100, 100), insets);
final Canvas mockCanvas = mock(Canvas.class);
when(mockCanvas.getWidth()).thenReturn(100);
when(mockCanvas.getHeight()).thenReturn(100);
- mWindow.drawNavigationBarBackground(mockCanvas);
+ mSnapshotSurface.drawNavigationBarBackground(mockCanvas);
verify(mockCanvas).drawRect(eq(new Rect(90, 0, 100, 100)), any());
}
}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f370ebd..9d6b29e 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -17,6 +17,7 @@
package android.window;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
@@ -60,8 +61,8 @@
private OnBackAnimationCallback mCallback1;
@Mock
private OnBackAnimationCallback mCallback2;
- @Mock
- private BackEvent mBackEvent;
+ private final BackMotionEvent mBackEvent = new BackMotionEvent(
+ 0, 0, 0, BackEvent.EDGE_LEFT, null);
@Before
public void setUp() throws Exception {
@@ -89,12 +90,12 @@
captor.capture());
captor.getAllValues().get(0).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
verifyZeroInteractions(mCallback2);
captor.getAllValues().get(1).getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
verifyNoMoreInteractions(mCallback1);
}
@@ -114,7 +115,7 @@
assertEquals(captor.getValue().getPriority(), OnBackInvokedDispatcher.PRIORITY_OVERLAY);
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback1).onBackStarted(mBackEvent);
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
}
@Test
@@ -152,6 +153,6 @@
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
captor.getValue().getCallback().onBackStarted(mBackEvent);
waitForIdle();
- verify(mCallback2).onBackStarted(mBackEvent);
+ verify(mCallback2).onBackStarted(any(BackEvent.class));
}
}
diff --git a/core/tests/fuzzers/FuzzService/FuzzBinder.java b/core/tests/fuzzers/FuzzService/FuzzBinder.java
index 52aafeb..7fd199a 100644
--- a/core/tests/fuzzers/FuzzService/FuzzBinder.java
+++ b/core/tests/fuzzers/FuzzService/FuzzBinder.java
@@ -34,12 +34,12 @@
fuzzServiceInternal(binder, data);
}
- // This API creates random parcel object
- public static void createRandomParcel(Parcel parcel, byte[] data) {
- getRandomParcel(parcel, data);
+ // This API fills parcel object
+ public static void fillRandomParcel(Parcel parcel, byte[] data) {
+ fillParcelInternal(parcel, data);
}
private static native void fuzzServiceInternal(IBinder binder, byte[] data);
- private static native void getRandomParcel(Parcel parcel, byte[] data);
+ private static native void fillParcelInternal(Parcel parcel, byte[] data);
private static native int registerNatives();
}
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
index dbeae87..264aa5f 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
@@ -38,7 +38,7 @@
return registerFrameworkNatives(env);
}
-JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
+JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
uint8_t data[len];
env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.h b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
index bc18b2f..c96354a 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.h
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
@@ -24,5 +24,5 @@
// Function from AndroidRuntime
jint registerFrameworkNatives(JNIEnv* env);
- JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
+ JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
}
diff --git a/core/tests/fuzzers/ParcelFuzzer/Android.bp b/core/tests/fuzzers/ParcelFuzzer/Android.bp
new file mode 100644
index 0000000..b71a06e
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/Android.bp
@@ -0,0 +1,40 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_fuzz {
+ name: "java_binder_parcel_fuzzer",
+ srcs: [
+ "ParcelFuzzer.java",
+ "ReadUtils.java",
+ "FuzzUtils.java",
+ "FuzzOperation.java",
+ "ReadOperation.java",
+ ":framework-core-sources-for-fuzzers",
+ ],
+ static_libs: [
+ "jazzer",
+ "random_parcel_lib",
+ "binderReadParcelIface-java",
+ ],
+ jni_libs: [
+ "librandom_parcel_jni",
+ "libc++",
+ "libandroid_runtime",
+ ],
+ libs: [
+ "framework",
+ "unsupportedappusage",
+ "ext",
+ "framework-res",
+ ],
+ native_bridge_supported: true,
+ fuzz_config: {
+ cc: [
+ "smoreland@google.com",
+ "waghpawan@google.com",
+ ],
+ // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+ hotlists: ["4637097"],
+ },
+}
diff --git a/core/java/android/window/BackEvent.aidl b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
similarity index 78%
copy from core/java/android/window/BackEvent.aidl
copy to core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
index 821f1fa..033231d 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package parcelfuzzer;
-package android.window;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
-/**
- * @hide
- */
-parcelable BackEvent;
+public interface FuzzOperation {
+ void doFuzz(FuzzedDataProvider data);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
new file mode 100644
index 0000000..5e9e5ec
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.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 parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class FuzzUtils {
+ public static FuzzOperation[] FUZZ_OPERATIONS =
+ new FuzzOperation[] {
+ new FuzzOperation() {
+ @java.lang.Override
+ public void doFuzz(FuzzedDataProvider provider) {
+ // Fuzz Append
+ int start = provider.consumeInt();
+ int len = provider.consumeInt();
+ Parcel p1 = null;
+ Parcel p2 = null;
+
+ try {
+ p1 = Parcel.obtain();
+ p2 = Parcel.obtain();
+
+ byte[] data =
+ provider.consumeBytes(
+ provider.consumeInt(0, provider.remainingBytes()));
+ FuzzBinder.fillRandomParcel(p1, data);
+ FuzzBinder.fillRandomParcel(p2, provider.consumeRemainingAsBytes());
+
+ p1.appendFrom(p2, start, len);
+
+ } catch (Exception e) {
+ // Rethrow exception as runtime exceptions are catched
+ // at highest level.
+ throw e;
+ } finally {
+ p1.recycle();
+ p2.recycle();
+ }
+ }
+ },
+ new FuzzOperation() {
+ @java.lang.Override
+ public void doFuzz(FuzzedDataProvider provider) {
+ // Fuzz Read
+ // Use maximum bytes to generate read instructions and remaining for parcel
+ // creation
+ int maxParcelBytes = provider.remainingBytes() / 3;
+ byte[] data = provider.consumeBytes(maxParcelBytes);
+ Parcel randomParcel = null;
+
+ try {
+ randomParcel = Parcel.obtain();
+ FuzzBinder.fillRandomParcel(randomParcel, data);
+
+ while (provider.remainingBytes() > 0) {
+ provider.pickValue(ReadUtils.READ_OPERATIONS)
+ .readParcel(randomParcel, provider);
+ }
+
+ } catch (Exception e) {
+ // Rethrow exception as runtime exceptions are catched
+ // at highest level.
+ throw e;
+ } finally {
+ randomParcel.recycle();
+ }
+ }
+ },
+ };
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
new file mode 100644
index 0000000..688c812
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
@@ -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 parcelfuzzer;
+
+import android.util.Log;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class ParcelFuzzer {
+
+ static {
+ // Initialize JNI dependencies
+ FuzzBinder.init();
+ }
+
+ public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
+ // Default behavior for Java APIs is to throw RuntimeException.
+ // We need to fuzz to detect other problems which are not handled explicitly.
+ // TODO(b/150808347): Change known API exceptions to subclass of
+ // RuntimeExceptions and catch those only.
+ try {
+ provider.pickValue(FuzzUtils.FUZZ_OPERATIONS).doFuzz(provider);
+ } catch (RuntimeException e) {
+ Log.e("ParcelFuzzer", "Exception occurred while fuzzing ", e);
+ }
+ }
+}
diff --git a/core/java/android/window/BackEvent.aidl b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
similarity index 74%
copy from core/java/android/window/BackEvent.aidl
copy to core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
index 821f1fa..5c227e3 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
@@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package parcelfuzzer;
-package android.window;
+import android.os.Parcel;
-/**
- * @hide
- */
-parcelable BackEvent;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface ReadOperation {
+ void readParcel(Parcel parcel, FuzzedDataProvider provider);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
new file mode 100644
index 0000000..0eff5f2
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
@@ -0,0 +1,435 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import parcelables.EmptyParcelable;
+import parcelables.GenericDataParcelable;
+import parcelables.SingleDataParcelable;
+
+public class ReadUtils {
+ public static int MAX_LEN = 1000000;
+ public static int MIN_LEN = 0;
+
+ private static class SomeParcelable implements Parcelable {
+ private final int mValue;
+
+ private SomeParcelable(Parcel in) {
+ this.mValue = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mValue);
+ }
+
+ public static Parcelable.Creator<SomeParcelable> CREATOR =
+ new Parcelable.Creator<SomeParcelable>() {
+
+ @Override
+ public SomeParcelable createFromParcel(Parcel source) {
+ return new SomeParcelable(source);
+ }
+
+ @Override
+ public SomeParcelable[] newArray(int size) {
+ return new SomeParcelable[size];
+ }
+ };
+ }
+
+ private static class TestClassLoader extends ClassLoader {
+ TestClassLoader() {
+ super();
+ }
+ }
+
+ private static class TestInterface implements IInterface {
+ public Binder binder;
+ private static final String DESCRIPTOR = "TestInterface";
+
+ TestInterface() {
+ binder = new Binder();
+ binder.attachInterface(this, DESCRIPTOR);
+ }
+
+ public IBinder asBinder() {
+ return binder;
+ }
+
+ public static TestInterface asInterface(IBinder binder) {
+ if (binder != null) {
+ IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
+ if (iface != null && iface instanceof TestInterface) {
+ return (TestInterface) iface;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static ReadOperation[] READ_OPERATIONS =
+ new ReadOperation[] {
+ (parcel, provider) -> {
+ parcel.setDataPosition(provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ parcel.setDataCapacity(provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ parcel.setDataSize(provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ parcel.dataSize();
+ },
+ (parcel, provider) -> {
+ parcel.dataPosition();
+ },
+ (parcel, provider) -> {
+ parcel.dataCapacity();
+ },
+
+ // read basic types
+ (parcel, provider) -> {
+ parcel.readByte();
+ },
+ (parcel, provider) -> {
+ parcel.readBoolean();
+ },
+ (parcel, provider) -> {
+ parcel.readInt();
+ },
+ (parcel, provider) -> {
+ parcel.readLong();
+ },
+ (parcel, provider) -> {
+ parcel.readFloat();
+ },
+ (parcel, provider) -> {
+ parcel.readDouble();
+ },
+ (parcel, provider) -> {
+ parcel.readString();
+ },
+ (parcel, provider) -> {
+ parcel.readString8();
+ },
+ (parcel, provider) -> {
+ parcel.readString16();
+ },
+ (parcel, provider) -> {
+ parcel.readBlob();
+ },
+ (parcel, provider) -> {
+ parcel.readStrongBinder();
+ },
+
+ // read arrays of random length
+ (parcel, provider) -> {
+ byte[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new byte[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new byte[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readByteArray(array);
+ },
+ (parcel, provider) -> {
+ char[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new char[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new char[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readCharArray(array);
+ },
+ (parcel, provider) -> {
+ int[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new int[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new int[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readIntArray(array);
+ },
+ (parcel, provider) -> {
+ double[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new double[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new double[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readDoubleArray(array);
+ },
+ (parcel, provider) -> {
+ float[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new float[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new float[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readFloatArray(array);
+ },
+ (parcel, provider) -> {
+ boolean[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new boolean[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new boolean[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readBooleanArray(array);
+ },
+ (parcel, provider) -> {
+ long[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new long[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new long[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readLongArray(array);
+ },
+ (parcel, provider) -> {
+ IBinder[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new IBinder[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new IBinder[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readBinderArray(array);
+ },
+ (parcel, provider) -> {
+ ArrayList<IBinder> arrayList = new ArrayList<IBinder>();
+ parcel.readBinderList(arrayList);
+ },
+
+ // unmarshall from random parcel data and random bytes
+ (parcel, provider) -> {
+ byte[] data = parcel.marshall();
+ Parcel p = Parcel.obtain();
+ p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+ p.recycle();
+ },
+ (parcel, provider) -> {
+ byte[] data = provider.consumeRemainingAsBytes();
+ Parcel p = Parcel.obtain();
+ p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+ p.recycle();
+ },
+ (parcel, provider) -> {
+ parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+ },
+
+ // read AIDL generated parcelables
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, SingleDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, SingleDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ SingleDataParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new SingleDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new SingleDataParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readTypedArray(array, SingleDataParcelable.CREATOR);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, EmptyParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, EmptyParcelable.class);
+ },
+ (parcel, provider) -> {
+ EmptyParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new EmptyParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new EmptyParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readTypedArray(array, EmptyParcelable.CREATOR);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, GenericDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, GenericDataParcelable.class);
+ },
+ (parcel, provider) -> {
+ GenericDataParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new GenericDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ int len = provider.consumeInt(MIN_LEN, MAX_LEN);
+ array = new GenericDataParcelable[len];
+ }
+ parcel.readTypedArray(array, GenericDataParcelable.CREATOR);
+ },
+
+ // read parcelables
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelable(loader, SomeParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader, SomeParcelable.class);
+ },
+ (parcel, provider) -> {
+ SomeParcelable[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new SomeParcelable[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new SomeParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readTypedArray(array, SomeParcelable.CREATOR);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader);
+ },
+ (parcel, provider) -> {
+ parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readParcelableArray(loader);
+ },
+
+ // read lists
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readArrayList(loader);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readArrayList(loader, Object.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readArrayList(loader, SomeParcelable.class);
+ },
+
+ // read sparse arrays
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSparseArray(loader);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSparseArray(loader, Object.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSparseArray(loader, SomeParcelable.class);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readSerializable(loader, Object.class);
+ },
+
+ // read interface
+ (parcel, provider) -> {
+ TestInterface[] array;
+ if (provider.consumeBoolean()) {
+ int pos = parcel.dataPosition();
+ array = new TestInterface[Math.min(MAX_LEN, parcel.readInt())];
+ parcel.setDataPosition(pos);
+ } else {
+ array = new TestInterface[provider.consumeInt(MIN_LEN, MAX_LEN)];
+ }
+ parcel.readInterfaceArray(array, TestInterface::asInterface);
+ },
+ (parcel, provider) -> {
+ int w = provider.consumeInt(MIN_LEN, MAX_LEN);
+ int h = provider.consumeInt(MIN_LEN, MAX_LEN);
+ TestInterface[][] array = new TestInterface[w][h];
+ parcel.readFixedArray(array, TestInterface::asInterface);
+ },
+ (parcel, provider) -> {
+ ArrayList<TestInterface> array = new ArrayList<TestInterface>();
+ parcel.readInterfaceList(array, TestInterface::asInterface);
+ },
+
+ // read bundle
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readBundle(loader);
+ },
+ (parcel, provider) -> {
+ parcel.readBundle();
+ },
+
+ // read HashMap
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readHashMap(loader);
+ },
+ (parcel, provider) -> {
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readHashMap(loader, String.class, String.class);
+ },
+ (parcel, provider) -> {
+ HashMap<String, String> hashMap = new HashMap<>();
+ TestClassLoader loader = new TestClassLoader();
+ parcel.readMap(hashMap, loader, String.class, String.class);
+ },
+ };
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp
index 625de14..6acb198 100644
--- a/core/tests/fuzzers/java_service_fuzzer/Android.bp
+++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp
@@ -37,4 +37,12 @@
"framework-res",
],
native_bridge_supported: true,
+ fuzz_config: {
+ cc: [
+ "smoreland@google.com",
+ "waghpawan@google.com",
+ ],
+ // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+ hotlists: ["4637097"],
+ },
}
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTest.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
index 0c939ec..9ccf3b3 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTest.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -27,6 +27,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.Resources;
@@ -399,6 +401,15 @@
verifyRealMetricsMatchesBounds(display, sDeviceBoundsLandscape);
}
+ @Test
+ public void testSupportedHdrTypesForDisplayModeAreSorted() {
+ int[] nonSortedHdrTypes = new int[]{3, 2, 1};
+ Display.Mode displayMode = new Display.Mode(0, 0, 0, 0, new float[0], nonSortedHdrTypes);
+
+ int[] sortedHdrTypes = new int[]{1, 2, 3};
+ assertArrayEquals(sortedHdrTypes, displayMode.getSupportedHdrTypes());
+ }
+
// Given rotated display dimensions, calculate the letterboxed app bounds.
private static Rect buildAppBounds(int displayWidth, int displayHeight) {
final int midWidth = displayWidth / 2;
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 4cc06e3..3346740 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -4171,6 +4171,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1945495497": {
+ "message": "Focused window didn't have a valid surface drawn.",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/BackNavigationController.java"
+ },
"1947239194": {
"message": "Deferring rotation, still finishing previous rotation",
"level": "VERBOSE",
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index c93b733..68f2927 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -26,7 +26,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
UNKNOWN,
- /**
+ /*
* Since some APIs accept either ImageFormat or PixelFormat (and the two
* enums do not overlap since they're both partial versions of the
* internal format enum), add PixelFormat values here so linting
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 1a81dda..6536e43 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -138,6 +138,16 @@
* provisioning server refuses key issuance, this is a permanent error.</p>
*/
public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16;
+ /**
+ * This device requires a software upgrade to use the key provisioning server. The software
+ * is outdated and this error is returned only on devices that rely solely on
+ * remotely-provisioned keys (see <a href=
+ * "https://android-developers.googleblog.com/2022/03/upgrading-android-attestation-remote.html"
+ * >Remote Key Provisioning</a>).
+ *
+ * @hide
+ */
+ public static final int ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION = 17;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -157,7 +167,8 @@
ERROR_INCORRECT_USAGE,
ERROR_KEY_NOT_TEMPORALLY_VALID,
ERROR_KEY_OPERATION_EXPIRED,
- ERROR_ATTESTATION_KEYS_UNAVAILABLE
+ ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+ ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION,
})
public @interface PublicErrorCode {
}
@@ -184,6 +195,16 @@
* This value is returned when {@link #isTransientFailure()} is {@code true}.
*/
public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3;
+ /**
+ * Re-try the operation that led to this error when the device has a software update
+ * downloaded and on the next reboot. The Remote provisioning server recognizes
+ * the device, but refuses issuance of attestation keys because it contains a software
+ * version that could potentially be vulnerable and needs an update. Re-trying after the
+ * device has upgraded and rebooted may alleviate the problem.
+ *
+ * <p>This value is returned when {@link #isTransientFailure()} is {@code true}.
+ */
+ public static final int RETRY_AFTER_NEXT_REBOOT = 4;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -191,6 +212,7 @@
RETRY_NEVER,
RETRY_WITH_EXPONENTIAL_BACKOFF,
RETRY_WHEN_CONNECTIVITY_AVAILABLE,
+ RETRY_AFTER_NEXT_REBOOT,
})
public @interface RetryPolicy {
}
@@ -217,6 +239,13 @@
* when the device has connectivity again.
* @hide */
public static final int RKP_FETCHING_PENDING_CONNECTIVITY = 3;
+ /**
+ * The RKP server recognizes the device, but the device may be running vulnerable software,
+ * and thus refusing issuance of RKP keys to it.
+ *
+ * @hide
+ */
+ public static final int RKP_FETCHING_PENDING_SOFTWARE_REBOOT = 4;
// Constants for encoding information about the error encountered:
// Whether the error relates to the system state/implementation as a whole, or a specific key.
@@ -236,7 +265,7 @@
private static int initializeRkpStatusForRegularErrors(int errorCode) {
// Check if the system code mistakenly called a constructor of KeyStoreException with
// the OUT_OF_KEYS error code but without RKP status.
- if (errorCode == ResponseCode.OUT_OF_KEYS) {
+ if (isRkpRelatedError(errorCode)) {
Log.e(TAG, "RKP error code without RKP status");
// Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
return RKP_SERVER_REFUSED_ISSUANCE;
@@ -272,7 +301,7 @@
super(message);
mErrorCode = errorCode;
mRkpStatus = rkpStatus;
- if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
+ if (!isRkpRelatedError(mErrorCode)) {
Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
}
}
@@ -309,10 +338,11 @@
public boolean isTransientFailure() {
PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
// Special-case handling for RKP failures:
- if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
+ if (mRkpStatus != RKP_SUCCESS && isRkpRelatedError(mErrorCode)) {
switch (mRkpStatus) {
case RKP_TEMPORARILY_UNAVAILABLE:
case RKP_FETCHING_PENDING_CONNECTIVITY:
+ case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
return true;
case RKP_SERVER_REFUSED_ISSUANCE:
default:
@@ -346,6 +376,11 @@
return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
}
+ private static boolean isRkpRelatedError(int errorCode) {
+ return errorCode == ResponseCode.OUT_OF_KEYS
+ || errorCode == ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE;
+ }
+
/**
* Returns the re-try policy for transient failures. Valid only if
* {@link #isTransientFailure()} returns {@code True}.
@@ -362,6 +397,8 @@
return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
case RKP_SERVER_REFUSED_ISSUANCE:
return RETRY_NEVER;
+ case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
+ return RETRY_AFTER_NEXT_REBOOT;
default:
return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
@@ -620,5 +657,8 @@
new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+ sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE,
+ new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+ ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index acc0005..2830d7eff 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -52,6 +52,7 @@
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -647,6 +648,7 @@
// {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
// {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
// {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
+ // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
public final int rkpStatus;
@Nullable
public final KeyPair keyPair;
@@ -856,6 +858,13 @@
KeymasterDefs.KM_TAG_ATTESTATION_ID_IMEI,
imei.getBytes(StandardCharsets.UTF_8)
));
+ final String secondImei = telephonyService.getImei(1);
+ if (!TextUtils.isEmpty(secondImei)) {
+ params.add(KeyStore2ParameterUtils.makeBytes(
+ KeymasterDefs.KM_TAG_ATTESTATION_ID_SECOND_IMEI,
+ secondImei.getBytes(StandardCharsets.UTF_8)
+ ));
+ }
break;
}
case AttestationUtils.ID_TYPE_MEID: {
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index e6ae282..2994593 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- 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.
--->
+ ~ 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="48dp"
android:height="48dp"
@@ -25,13 +25,12 @@
android:fillAlpha="0.8"
android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
<group
- android:translateX="12"
- android:translateY="12">
+ android:scaleX="0.8"
+ android:scaleY="0.8"
+ android:translateX="10"
+ android:translateY="10">
<path
- android:fillColor="@color/compat_controls_text"
- android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
- <path
- android:fillColor="@color/compat_controls_text"
- android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+ android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+ android:fillColor="@color/compat_controls_text"/>
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 0248719..51de2e5 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"መተግበሪያ ከተከፈለ ማያ ገጽ ጋር ላይሠራ ይችላል"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"መተግበሪያው የተከፈለ ማያ ገጽን አይደግፍም።"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ይህ መተግበሪያ መከፈት የሚችለው በ1 መስኮት ብቻ ነው።"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"መተግበሪያ በሁለተኛ ማሳያ ላይ ላይሠራ ይችላል።"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"መተግበሪያ በሁለተኛ ማሳያዎች ላይ ማስጀመርን አይደግፍም።"</string>
<string name="accessibility_divider" msgid="703810061635792791">"የተከፈለ የማያ ገጽ ከፋይ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index cc7df4a..635334d 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"إظهار"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"قد لا يعمل التطبيق بشكل سليم في وضع \"تقسيم الشاشة\"."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"التطبيق لا يتيح تقسيم الشاشة."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"لا يمكن فتح هذا التطبيق إلا في نافذة واحدة."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"قد لا يعمل التطبيق على شاشة عرض ثانوية."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"لا يمكن تشغيل التطبيق على شاشات عرض ثانوية."</string>
<string name="accessibility_divider" msgid="703810061635792791">"أداة تقسيم الشاشة"</string>
diff --git a/libs/WindowManager/Shell/res/values-eu/strings.xml b/libs/WindowManager/Shell/res/values-eu/strings.xml
index f3e9b8f..4417668 100644
--- a/libs/WindowManager/Shell/res/values-eu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-eu/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Ez gorde"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Baliteke aplikazioak ez funtzionatzea pantaila zatituan."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Aplikazioak ez du onartzen pantaila zatitua"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Leiho bakar batean ireki daiteke aplikazioa."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Baliteke aplikazioak ez funtzionatzea bigarren mailako pantailetan."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Aplikazioa ezin da abiarazi bigarren mailako pantailatan."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Pantaila-zatitzailea"</string>
diff --git a/libs/WindowManager/Shell/res/values-gu/strings.xml b/libs/WindowManager/Shell/res/values-gu/strings.xml
index c50445c..e680298 100644
--- a/libs/WindowManager/Shell/res/values-gu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-gu/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"બતાવો"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"વિભાજિત-સ્ક્રીન સાથે ઍપ કદાચ કામ ન કરે."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"ઍપ્લિકેશન સ્ક્રીન-વિભાજનનું સમર્થન કરતી નથી."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"આ ઍપ માત્ર 1 વિન્ડોમાં ખોલી શકાય છે."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર કદાચ કામ ન કરે."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ઍપ્લિકેશન ગૌણ ડિસ્પ્લે પર લૉન્ચનું સમર્થન કરતી નથી."</string>
<string name="accessibility_divider" msgid="703810061635792791">"સ્પ્લિટ-સ્ક્રીન વિભાજક"</string>
diff --git a/libs/WindowManager/Shell/res/values-my/strings.xml b/libs/WindowManager/Shell/res/values-my/strings.xml
index 6f8fed9..b2bb37d 100644
--- a/libs/WindowManager/Shell/res/values-my/strings.xml
+++ b/libs/WindowManager/Shell/res/values-my/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"မသိုဝှက်ရန်"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"မျက်နှာပြင် ခွဲ၍ပြသခြင်းဖြင့် အက်ပ်သည် အလုပ်မလုပ်ပါ။"</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"အက်ပ်သည် မျက်နှာပြင်ခွဲပြရန် ပံ့ပိုးထားခြင်းမရှိပါ။"</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ဤအက်ပ်ကို ဝင်းဒိုး ၁ ခုတွင်သာ ဖွင့်နိုင်သည်။"</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"ဤအက်ပ်အနေဖြင့် ဒုတိယဖန်သားပြင်ပေါ်တွင် အလုပ်လုပ်မည် မဟုတ်ပါ။"</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"ဤအက်ပ်အနေဖြင့် ဖွင့်ရန်စနစ်ကို ဒုတိယဖန်သားပြင်မှ အသုံးပြုရန် ပံ့ပိုးမထားပါ။"</string>
<string name="accessibility_divider" msgid="703810061635792791">"မျက်နှာပြင်ခွဲခြမ်း ပိုင်းခြားပေးသည့်စနစ်"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
index 8cf3314..69be68e 100644
--- a/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rBR/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt/strings.xml b/libs/WindowManager/Shell/res/values-pt/strings.xml
index 8cf3314..69be68e 100644
--- a/libs/WindowManager/Shell/res/values-pt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Exibir"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"É possível que o app não funcione com a tela dividida."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"O app não é compatível com a divisão de tela."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Este app só pode ser aberto em uma única janela."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"É possível que o app não funcione em uma tela secundária."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"O app não é compatível com a inicialização em telas secundárias."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Divisor de tela"</string>
diff --git a/libs/WindowManager/Shell/res/values-sw/strings.xml b/libs/WindowManager/Shell/res/values-sw/strings.xml
index fa46229..345fbf8 100644
--- a/libs/WindowManager/Shell/res/values-sw/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sw/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Fichua"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Huenda programu isifanye kazi kwenye skrini inayogawanywa."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Programu haiwezi kutumia skrini iliyogawanywa."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Programu hii inaweza kufunguliwa katika dirisha 1 pekee."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Huenda programu isifanye kazi kwenye dirisha lingine."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Programu hii haiwezi kufunguliwa kwenye madirisha mengine."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Kitenganishi cha skrini inayogawanywa"</string>
diff --git a/libs/WindowManager/Shell/res/values-uk/strings.xml b/libs/WindowManager/Shell/res/values-uk/strings.xml
index 084fdb2..b589ed8 100644
--- a/libs/WindowManager/Shell/res/values-uk/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uk/strings.xml
@@ -34,8 +34,7 @@
<string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Показати"</string>
<string name="dock_forced_resizable" msgid="1749750436092293116">"Додаток може не працювати в режимі розділеного екрана."</string>
<string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"Додаток не підтримує розділення екрана."</string>
- <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
- <skip />
+ <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"Цей додаток можна відкрити лише в одному вікні."</string>
<string name="forced_resizable_secondary_display" msgid="1768046938673582671">"Додаток може не працювати на додатковому екрані."</string>
<string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"Додаток не підтримує запуск на додаткових екранах."</string>
<string name="accessibility_divider" msgid="703810061635792791">"Розділювач екрана"</string>
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 0133f6b..57a0fd5 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
@@ -47,6 +47,7 @@
import android.view.RemoteAnimationTarget;
import android.window.BackAnimationAdapter;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationRunner;
@@ -385,7 +386,7 @@
return;
}
- final BackEvent backEvent = mTouchTracker.createProgressEvent();
+ final BackMotionEvent backEvent = mTouchTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backEvent);
}
@@ -415,7 +416,7 @@
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -453,7 +454,7 @@
}
private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
- BackEvent backEvent) {
+ BackMotionEvent backEvent) {
if (callback == null) {
return;
}
@@ -466,6 +467,11 @@
}
}
+ private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
+ // TODO(b/258698745): Only dispatch to animation callbacks.
+ return mEnableAnimations.get();
+ }
+
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
@@ -640,7 +646,7 @@
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();
+ final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backFinish);
startPostCommitAnimation();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index 9f6bc5d..e36e16c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -39,6 +39,7 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
@@ -315,13 +316,13 @@
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
CrossActivityAnimation.this::onGestureProgress);
}
@Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a9a7b77..676e259 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -39,6 +39,7 @@
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
@@ -316,13 +317,13 @@
private final class Callback extends IOnBackInvokedCallback.Default {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backEvent) {
mProgressAnimator.onBackStarted(backEvent,
CrossTaskBackAnimation.this::onGestureProgress);
}
@Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
+ public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
mProgressAnimator.onBackProgressed(backEvent);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index ccfac65..695ef4e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -19,6 +19,7 @@
import android.os.SystemProperties;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
/**
* Helper class to record the touch location for gesture and generate back events.
@@ -82,11 +83,11 @@
mSwipeEdge = BackEvent.EDGE_LEFT;
}
- BackEvent createStartEvent(RemoteAnimationTarget target) {
- return new BackEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
+ return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
}
- BackEvent createProgressEvent() {
+ BackMotionEvent createProgressEvent() {
float progressThreshold = PROGRESS_THRESHOLD >= 0
? PROGRESS_THRESHOLD : mProgressThreshold;
progressThreshold = progressThreshold == 0 ? 1 : progressThreshold;
@@ -109,8 +110,8 @@
return createProgressEvent(progress);
}
- BackEvent createProgressEvent(float progress) {
- return new BackEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ BackMotionEvent createProgressEvent(float progress) {
+ return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
}
public void setProgressThreshold(float progressThreshold) {
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 6e116b9..c836b95 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
@@ -51,8 +51,6 @@
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.SurfaceUtils;
-import java.util.function.Consumer;
-
/**
* Handles split decor like showing resizing hint for a specific split.
*/
@@ -72,17 +70,18 @@
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
private SurfaceControl mGapBackgroundLeash;
+ private SurfaceControl mScreenshot;
private boolean mShown;
private boolean mIsResizing;
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;
+ private int mRunningAnimationCount = 0;
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
@@ -173,7 +172,6 @@
mIsResizing = true;
mBounds.set(newBounds);
}
- mResizingBounds.set(newBounds);
mOffsetX = offsetX;
mOffsetY = offsetY;
@@ -227,33 +225,41 @@
t.setVisibility(mBackgroundLeash, show);
t.setVisibility(mIconLeash, show);
} else {
- startFadeAnimation(show, null /* finishedConsumer */);
+ startFadeAnimation(show, false, null);
}
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);
+ public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+ if (mScreenshot != null) {
+ t.setPosition(mScreenshot, mOffsetX, mOffsetY);
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.setAlpha(mScreenshot, progress);
animT.apply();
});
va.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ mRunningAnimationCount++;
+ }
+
+ @Override
public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
- animT.remove(screenshot);
+ mRunningAnimationCount--;
+ animT.remove(mScreenshot);
animT.apply();
animT.close();
+ mScreenshot = null;
+
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.run();
+ }
}
});
va.start();
@@ -285,10 +291,34 @@
mFadeAnimator.cancel();
}
if (mShown) {
- fadeOutDecor(null /* finishedCallback */);
+ fadeOutDecor(animFinishedCallback);
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
+ if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+ animFinishedCallback.run();
+ }
+ }
+ }
+
+ /** Screenshot host leash and attach on it if meet some conditions */
+ public void screenshotIfNeeded(SurfaceControl.Transaction t) {
+ if (!mShown && mIsResizing) {
+ mTempRect.set(mBounds);
+ mTempRect.offsetTo(0, 0);
+ mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
+ Integer.MAX_VALUE - 1);
+ }
+ }
+
+ /** Set screenshot and attach on host leash it if meet some conditions */
+ public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
+ if (screenshot == null || !screenshot.isValid()) return;
+
+ if (!mShown && mIsResizing) {
+ mScreenshot = screenshot;
+ t.reparent(screenshot, mHostLeash);
+ t.setLayer(screenshot, Integer.MAX_VALUE - 1);
}
}
@@ -296,18 +326,15 @@
* directly. */
public void fadeOutDecor(Runnable finishedCallback) {
if (mShown) {
- startFadeAnimation(false /* show */, transaction -> {
- releaseDecor(transaction);
- if (finishedCallback != null) finishedCallback.run();
- });
+ startFadeAnimation(false /* show */, true, finishedCallback);
mShown = false;
} else {
if (finishedCallback != null) finishedCallback.run();
}
}
- private void startFadeAnimation(boolean show,
- Consumer<SurfaceControl.Transaction> finishedConsumer) {
+ private void startFadeAnimation(boolean show, boolean releaseSurface,
+ Runnable finishedCallback) {
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
mFadeAnimator.setDuration(FADE_DURATION);
@@ -324,6 +351,7 @@
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(@NonNull Animator animation) {
+ mRunningAnimationCount++;
if (show) {
animT.show(mBackgroundLeash).show(mIconLeash);
}
@@ -335,6 +363,7 @@
@Override
public void onAnimationEnd(@NonNull Animator animation) {
+ mRunningAnimationCount--;
if (!show) {
if (mBackgroundLeash != null) {
animT.hide(mBackgroundLeash);
@@ -343,11 +372,15 @@
animT.hide(mIconLeash);
}
}
- if (finishedConsumer != null) {
- finishedConsumer.accept(animT);
+ if (releaseSurface) {
+ releaseDecor(animT);
}
animT.apply();
animT.close();
+
+ if (mRunningAnimationCount == 0 && finishedCallback != null) {
+ finishedCallback.run();
+ }
}
});
mFadeAnimator.start();
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 4ea8a5d..dc1634a 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
@@ -697,10 +697,13 @@
@WMSingleton
@Provides
- static Optional<DesktopModeController> providesDesktopModeController(
- @DynamicOverride Optional<DesktopModeController> desktopModeController) {
+ static Optional<DesktopModeController> provideDesktopModeController(
+ @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
if (DesktopModeStatus.IS_SUPPORTED) {
- return desktopModeController;
+ return desktopModeController.map(Lazy::get);
}
return Optional.empty();
}
@@ -711,10 +714,13 @@
@WMSingleton
@Provides
- static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
- @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
+ static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+ @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+ // Use optional-of-lazy for the dependency that this provider relies on.
+ // Lazy ensures that this provider will not be the cause the dependency is created
+ // when it will not be returned due to the condition below.
if (DesktopModeStatus.IS_SUPPORTED) {
- return desktopModeTaskRepository;
+ return desktopModeTaskRepository.map(Lazy::get);
}
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 f1670cd..6be8305 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
@@ -189,7 +189,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- @DynamicOverride DesktopModeController desktopModeController) {
+ Optional<DesktopModeController> desktopModeController) {
return new CaptionWindowDecorViewModel(
context,
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 abc4024..5824f51 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
@@ -64,6 +64,7 @@
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.List;
import java.util.concurrent.Executor;
/**
@@ -99,7 +100,9 @@
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
- shellInit.addInitCallback(this::onInit, this);
+ if (DesktopModeStatus.isSupported()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
@@ -258,18 +261,36 @@
@NonNull
private WindowContainerTransaction bringDesktopAppsToFront() {
- ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
- ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+ final List<RunningTaskInfo> taskInfos = new ArrayList<>();
for (Integer taskId : activeTasks) {
RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
if (taskInfo != null) {
taskInfos.add(taskInfo);
}
}
- // Order by lastActiveTime, descending
- taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
- WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ if (taskInfos.isEmpty()) {
+ return wct;
+ }
+
+ final boolean allActiveTasksAreVisible = taskInfos.stream()
+ .allMatch(info -> mDesktopModeTaskRepository.isVisibleTask(info.taskId));
+ if (allActiveTasksAreVisible) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: active tasks are already in front, skipping.");
+ return wct;
+ }
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "bringDesktopAppsToFront: reordering all active tasks to the front");
+ final List<Integer> allTasksInZOrder =
+ mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+ // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+ // in the WCT.
+ taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 2fafe67..e3eb2b7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -37,6 +37,13 @@
"persist.wm.debug.desktop_mode", false);
/**
+ * Return {@code true} if desktop mode support is enabled
+ */
+ public static boolean isSupported() {
+ return IS_SUPPORTED;
+ }
+
+ /**
* Check if desktop mode is active
*
* @return {@code true} if active
@@ -54,4 +61,5 @@
return false;
}
}
+
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index b7749fc..600ccc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -33,6 +33,8 @@
*/
private val activeTasks = ArraySet<Int>()
private val visibleTasks = ArraySet<Int>()
+ // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+ private val freeformTasksInZOrder = mutableListOf<Int>()
private val activeTasksListeners = ArraySet<ActiveTasksListener>()
// Track visible tasks separately because a task may be part of the desktop but not visible.
private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -101,6 +103,13 @@
}
/**
+ * Whether a task is visible.
+ */
+ fun isVisibleTask(taskId: Int): Boolean {
+ return visibleTasks.contains(taskId)
+ }
+
+ /**
* Get a set of the active tasks
*/
fun getActiveTasks(): ArraySet<Int> {
@@ -108,6 +117,13 @@
}
/**
+ * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+ */
+ fun getFreeformTasksInZOrder(): List<Int> {
+ return freeformTasksInZOrder
+ }
+
+ /**
* Updates whether a freeform task with this id is visible or not and notifies listeners.
*/
fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
@@ -127,6 +143,23 @@
}
/**
+ * Add (or move if it already exists) the task to the top of the ordered list.
+ */
+ fun addOrMoveFreeformTaskToTop(taskId: Int) {
+ if (freeformTasksInZOrder.contains(taskId)) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+ freeformTasksInZOrder.add(0, taskId)
+ }
+
+ /**
+ * Remove the task from the ordered list.
+ */
+ fun removeFreeformTask(taskId: Int) {
+ freeformTasksInZOrder.remove(taskId)
+ }
+
+ /**
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
*/
interface ActiveTasksListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
new file mode 100644
index 0000000..926cfb3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module desktop owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 44bcdb2..8a9b74f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -38,7 +38,8 @@
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
*/
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+ ShellTaskOrganizer.FocusListener {
private static final String TAG = "FreeformTaskListener";
private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -67,6 +68,9 @@
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+ if (DesktopModeStatus.IS_SUPPORTED) {
+ mShellTaskOrganizer.addFocusListener(this);
+ }
}
@Override
@@ -86,13 +90,16 @@
t.apply();
}
- if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
+ if (DesktopModeStatus.IS_SUPPORTED) {
mDesktopModeTaskRepository.ifPresent(repository -> {
- if (repository.addActiveTask(taskInfo.taskId)) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
- "Adding active freeform task: #%d", taskInfo.taskId);
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ if (taskInfo.isVisible) {
+ if (repository.addActiveTask(taskInfo.taskId)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "Adding active freeform task: #%d", taskInfo.taskId);
+ }
+ repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
}
- repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
});
}
}
@@ -105,6 +112,7 @@
if (DesktopModeStatus.IS_SUPPORTED) {
mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Removing active freeform task: #%d", taskInfo.taskId);
@@ -140,6 +148,18 @@
}
@Override
+ public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+ "Freeform Task Focus Changed: #%d focused=%b",
+ taskInfo.taskId, taskInfo.isFocused);
+ if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isFocused) {
+ mDesktopModeTaskRepository.ifPresent(repository -> {
+ repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+ });
+ }
+ }
+
+ @Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
b.setParent(findTaskSurface(taskId));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
new file mode 100644
index 0000000..0c2d5c4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module freeform owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index f9172ba..db0f0bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -342,6 +342,16 @@
}
/**
+ * Returns the top running leaf task.
+ */
+ @Nullable
+ public ActivityManager.RunningTaskInfo getTopRunningTask() {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1,
+ false /* filterOnlyVisibleRecents */);
+ return tasks.isEmpty() ? null : tasks.get(0);
+ }
+
+ /**
* Find the background task that match the given component.
*/
@Nullable
@@ -367,6 +377,8 @@
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
+ pw.println(prefix + " mListener=" + mListener);
+ pw.println(prefix + "Tasks:");
ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d86aadc..2f2bc77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -73,6 +73,9 @@
/** Called when device waking up finished. */
void onFinishedWakingUp();
+ /** Called when requested to go to fullscreen from the current active split app. */
+ void goToFullscreenFromSplit();
+
/** Get a string representation of a stage type */
static String stageTypeToString(@StageType int stage) {
switch (stage) {
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 a79ac45..400039b 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
@@ -123,6 +123,7 @@
public static final int EXIT_REASON_SCREEN_LOCKED = 7;
public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+ public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -134,6 +135,7 @@
EXIT_REASON_SCREEN_LOCKED,
EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
+ EXIT_REASON_FULLSCREEN_SHORTCUT,
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -315,10 +317,6 @@
return mStageCoordinator;
}
- public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mStageCoordinator.getFocusingTaskInfo();
- }
-
public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
}
@@ -422,6 +420,10 @@
mStageCoordinator.unregisterSplitScreenListener(listener);
}
+ public void goToFullscreenFromSplit() {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
+
public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -628,9 +630,10 @@
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);
+ final ActivityManager.RunningTaskInfo topRunningTask = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.getTopRunningTask()).orElse(null);
+ if (topRunningTask != null && isValidToEnterSplitScreen(topRunningTask)) {
+ return Objects.equals(topRunningTask.baseIntent.getComponent(), launchingActivity);
}
return false;
}
@@ -863,9 +866,12 @@
@Override
public void onFinishedWakingUp() {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onFinishedWakingUp();
- });
+ mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+ }
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
}
}
@@ -921,33 +927,25 @@
@Override
public void exitSplitScreen(int toTopTaskId) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
- (controller) -> {
- controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
- });
+ (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
}
@Override
public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
- (controller) -> {
- controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
+ (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
}
@Override
public void removeFromSideStage(int taskId) {
executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
- (controller) -> {
- controller.removeFromSideStage(taskId);
- });
+ (controller) -> controller.removeFromSideStage(taskId));
}
@Override
public void startTask(int taskId, int position, @Nullable Bundle options) {
executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> {
- controller.startTask(taskId, position, options);
- });
+ (controller) -> controller.startTask(taskId, position, options));
}
@Override
@@ -1039,19 +1037,16 @@
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
- (controller) -> {
- controller.startShortcut(packageName, shortcutId, position, options, user,
- instanceId);
- });
+ (controller) -> controller.startShortcut(packageName, shortcutId, position,
+ options, user, instanceId));
}
@Override
public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
@Nullable Bundle options, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> {
- controller.startIntent(intent, fillInIntent, position, options, instanceId);
- });
+ (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+ instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 21a1310..1cf3a89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -47,6 +47,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.OneShotRemoteHandler;
import com.android.wm.shell.transition.Transitions;
@@ -64,6 +65,7 @@
DismissTransition mPendingDismiss = null;
TransitSession mPendingEnter = null;
TransitSession mPendingRecent = null;
+ TransitSession mPendingResize = null;
private IBinder mAnimatingTransition = null;
OneShotRemoteHandler mPendingRemoteHandler = null;
@@ -177,6 +179,43 @@
onFinish(null /* wct */, null /* wctCB */);
}
+ void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback,
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+ @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+ mFinishCallback = finishCallback;
+ mAnimatingTransition = transition;
+ mFinishTransaction = finishTransaction;
+
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.setPosition(leash, change.getEndAbsBounds().left,
+ change.getEndAbsBounds().top);
+ startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
+ change.getEndAbsBounds().height());
+
+ SplitDecorManager decor = mainRoot.equals(change.getContainer())
+ ? mainDecor : sideDecor;
+ ValueAnimator va = new ValueAnimator();
+ mAnimations.add(va);
+ decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
+ decor.onResized(startTransaction, () -> {
+ mTransitions.getMainExecutor().execute(() -> {
+ mAnimations.remove(va);
+ onFinish(null /* wct */, null /* wctCB */);
+ });
+ });
+ }
+ }
+
+ startTransaction.apply();
+ onFinish(null /* wct */, null /* wctCB */);
+ }
+
boolean isPendingTransition(IBinder transition) {
return getPendingTransition(transition) != null;
}
@@ -193,6 +232,10 @@
return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
}
+ boolean isPendingResize(IBinder transition) {
+ return mPendingResize != null && mPendingResize.mTransition == transition;
+ }
+
@Nullable
private TransitSession getPendingTransition(IBinder transition) {
if (isPendingEnter(transition)) {
@@ -201,11 +244,14 @@
return mPendingRecent;
} else if (isPendingDismiss(transition)) {
return mPendingDismiss;
+ } else if (isPendingResize(transition)) {
+ return mPendingResize;
}
return null;
}
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
@WindowManager.TransitionType int transitType,
@@ -258,6 +304,21 @@
exitReasonToString(reason), stageTypeToString(dismissTop));
}
+ IBinder startResizeTransition(WindowContainerTransaction wct,
+ Transitions.TransitionHandler handler,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
+ setResizeTransition(transition, finishCallback);
+ return transition;
+ }
+
+ void setResizeTransition(@NonNull IBinder transition,
+ @Nullable TransitionFinishedCallback finishCallback) {
+ mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Resize split screen");
+ }
+
void setRecentTransition(@NonNull IBinder transition,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionFinishedCallback finishCallback) {
@@ -324,6 +385,9 @@
mPendingRecent.onConsumed(aborted);
mPendingRecent = null;
mPendingRemoteHandler = null;
+ } else if (isPendingResize(transition)) {
+ mPendingResize.onConsumed(aborted);
+ mPendingResize = null;
}
}
@@ -340,6 +404,9 @@
} else if (isPendingDismiss(mAnimatingTransition)) {
mPendingDismiss.onFinished(wct, mFinishTransaction);
mPendingDismiss = null;
+ } else if (isPendingResize(mAnimatingTransition)) {
+ mPendingResize.onFinished(wct, mFinishTransaction);
+ mPendingResize = null;
}
mPendingRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 2dc4a04..1016e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -23,6 +23,7 @@
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -38,6 +39,7 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -180,6 +182,8 @@
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
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 4cb7623..38fb2df 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
@@ -49,6 +49,7 @@
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -150,7 +151,7 @@
*/
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
- ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
+ ShellTaskOrganizer.TaskListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
@@ -186,8 +187,6 @@
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
- private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
-
/**
* A single-top root task which the split divider attached to.
*/
@@ -304,7 +303,6 @@
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
- mTaskOrganizer.addFocusListener(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
}
@@ -455,8 +453,9 @@
}
/** Launches an activity into split by legacy transition. */
- void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
+ void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final boolean isEnteringSplit = !isSplitActive();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -466,22 +465,29 @@
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
- if (apps == null || apps.length == 0) {
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- mMainExecutor.execute(() ->
- exitSplitScreen(mMainStage.getChildCount() == 0
- ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
- mSplitUnsupportedToast.show();
+ if (isEnteringSplit) {
+ boolean openingToSide = false;
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING
+ && mSideStage.containsTask(apps[i].taskId)) {
+ openingToSide = true;
+ break;
+ }
+ }
}
-
- // Do nothing when the animation was cancelled.
- t.apply();
- return;
+ if (!openingToSide) {
+ mMainExecutor.execute(() -> exitSplitScreen(
+ mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+ EXIT_REASON_UNKNOWN));
+ }
}
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
+ if (apps != null) {
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
}
}
t.apply();
@@ -503,7 +509,7 @@
// If split still not active, apply windows bounds first to avoid surface reset to
// wrong pos by SurfaceAnimator from wms.
- if (!mMainStage.isActive() && mLogger.isEnterRequestedByDrag()) {
+ if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
@@ -1110,15 +1116,8 @@
* Exits the split screen by finishing one of the tasks.
*/
protected void exitStage(@SplitPosition int stageToClose) {
- if (ENABLE_SHELL_TRANSITIONS) {
- StageTaskListener stageToTop = mSideStagePosition == stageToClose
- ? mMainStage
- : mSideStage;
- exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
- } else {
- boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
- mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
- }
+ mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
}
/**
@@ -1152,6 +1151,9 @@
case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
// User has unlocked the device after folded
case EXIT_REASON_DEVICE_FOLDED:
+ // The device is folded
+ case EXIT_REASON_FULLSCREEN_SHORTCUT:
+ // User has used a keyboard shortcut to go back to fullscreen from split
return true;
default:
return false;
@@ -1615,15 +1617,6 @@
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
}
- ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
- return mFocusingTaskInfo;
- }
-
- @Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mFocusingTaskInfo = taskInfo;
- }
-
@Override
public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
@@ -1674,15 +1667,29 @@
public void onLayoutSizeChanged(SplitLayout layout) {
// Reset this flag every time onLayoutSizeChanged.
mShowDecorImmediately = false;
+
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ // Only need screenshot for legacy case because shell transition should screenshot
+ // itself during transition.
+ final SurfaceControl.Transaction startT = mTransactionPool.acquire();
+ mMainStage.screenshotIfNeeded(startT);
+ mSideStage.screenshotIfNeeded(startT);
+ mTransactionPool.release(startT);
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> {
- updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
- mMainStage.onResized(t);
- mSideStage.onResized(t);
- });
+ if (ENABLE_SHELL_TRANSITIONS) {
+ mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+ } else {
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t -> {
+ updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
+ mMainStage.onResized(t);
+ mSideStage.onResized(t);
+ });
+ }
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
@@ -2036,6 +2043,12 @@
} else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+ } else if (mSplitTransitions.isPendingResize(transition)) {
+ mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+ finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
+ mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
+ mSideStage.getSplitDecorManager());
+ return true;
}
if (!shouldAnimate) return false;
@@ -2123,6 +2136,16 @@
return true;
}
+ public void goToFullscreenFromSplit() {
+ boolean leftOrTop;
+ if (mSideStage.isFocused()) {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else {
+ leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ }
+ mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+ }
+
/** Synchronize split-screen state with transition and make appropriate preparations. */
public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
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 358f712..8a52c87 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
@@ -292,7 +292,13 @@
void onResized(SurfaceControl.Transaction t) {
if (mSplitDecorManager != null) {
- mSplitDecorManager.onResized(t);
+ mSplitDecorManager.onResized(t, null);
+ }
+ }
+
+ void screenshotIfNeeded(SurfaceControl.Transaction t) {
+ if (mSplitDecorManager != null) {
+ mSplitDecorManager.screenshotIfNeeded(t);
}
}
@@ -304,6 +310,10 @@
}
}
+ SplitDecorManager getSplitDecorManager() {
+ return mSplitDecorManager;
+ }
+
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 8bba4404..20da877 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -50,13 +50,17 @@
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
+ // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+ private final float mRoundedCornerRadius;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
- Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+ Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+ float roundedCornerRadius) {
mSplashScreenView = view;
mFirstWindowSurface = leash;
+ mRoundedCornerRadius = roundedCornerRadius;
if (frame != null) {
mFirstWindowFrame.set(frame);
}
@@ -97,7 +101,7 @@
SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this);
+ mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 3098e55..a7e4385 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -63,6 +63,24 @@
/**
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window with rounded corner radius.
+ */
+ static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
+ ValueAnimator animator =
+ createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, roundedCornerRadius);
+ animator.start();
+ }
+
+ /**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
* window.
* @hide
*/
@@ -71,12 +89,10 @@
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
- ValueAnimator animator =
- createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
- transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
- iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener);
- animator.start();
+ startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, 0f /* roundedCornerRadius */);
}
/**
@@ -87,7 +103,8 @@
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
// reveal app
final float transparentRatio = 0.8f;
final int globalHeight = splashScreenView.getHeight();
@@ -124,7 +141,7 @@
shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
- mMainWindowShiftLength);
+ mMainWindowShiftLength, roundedCornerRadius);
}
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
@@ -289,8 +306,8 @@
public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
- int mainWindowShiftLength) {
- mFromYDelta = fromYDelta;
+ int mainWindowShiftLength, float roundedCornerRadius) {
+ mFromYDelta = fromYDelta - roundedCornerRadius;
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 6ce981e..ebb957b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -16,8 +16,10 @@
package com.android.wm.shell.startingsurface;
+import static android.content.Context.CONTEXT_RESTRICTED;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
@@ -29,6 +31,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -48,9 +51,11 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -58,7 +63,9 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.ContextThemeWrapper;
+import android.view.Display;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.SplashScreenView;
import android.window.StartingWindowInfo;
import android.window.StartingWindowInfo.StartingWindowType;
@@ -134,6 +141,144 @@
}
/**
+ * Help method to create a layout parameters for a window.
+ */
+ static Context createContext(Context initContext, StartingWindowInfo windowInfo,
+ int theme, @StartingWindowInfo.StartingWindowType int suggestType,
+ DisplayManager displayManager) {
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ if (activityInfo == null || activityInfo.packageName == null) {
+ return null;
+ }
+
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
+ activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
+ final Display display = displayManager.getDisplay(displayId);
+ if (display == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return null;
+ }
+ Context context = displayId == DEFAULT_DISPLAY
+ ? initContext : initContext.createDisplayContext(display);
+ if (context == null) {
+ return null;
+ }
+ if (theme != context.getThemeResId()) {
+ try {
+ context = context.createPackageContextAsUser(activityInfo.packageName,
+ CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
+ context.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Failed creating package context with package name "
+ + activityInfo.packageName + " for user " + taskInfo.userId, e);
+ return null;
+ }
+ }
+
+ final Configuration taskConfig = taskInfo.getConfiguration();
+ if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: creating context based on task Configuration %s",
+ taskConfig);
+ final Context overrideContext = context.createConfigurationContext(taskConfig);
+ overrideContext.setTheme(theme);
+ final TypedArray typedArray = overrideContext.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ try {
+ if (resId != 0 && overrideContext.getDrawable(resId) != null) {
+ // We want to use the windowBackground for the override context if it is
+ // available, otherwise we use the default one to make sure a themed starting
+ // window is displayed for the app.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+ "addSplashScreen: apply overrideConfig %s",
+ taskConfig);
+ context = overrideContext;
+ }
+ } catch (Resources.NotFoundException e) {
+ Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
+ + taskId, e);
+ return null;
+ }
+ typedArray.recycle();
+ }
+ return context;
+ }
+
+ /**
+ * Creates the window layout parameters for splashscreen window.
+ */
+ static WindowManager.LayoutParams createLayoutParameters(Context context,
+ StartingWindowInfo windowInfo,
+ @StartingWindowInfo.StartingWindowType int suggestType,
+ CharSequence title, int pixelFormat, IBinder appToken) {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+ params.setFitInsetsSides(0);
+ params.setFitInsetsTypes(0);
+ params.format = pixelFormat;
+ int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+ final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+ if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ }
+ if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+ if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ } else {
+ windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+ }
+ params.layoutInDisplayCutoutMode = a.getInt(
+ R.styleable.Window_windowLayoutInDisplayCutoutMode,
+ params.layoutInDisplayCutoutMode);
+ params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+ a.recycle();
+
+ final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+ final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+ ? windowInfo.targetActivityInfo
+ : taskInfo.topActivityInfo;
+ final int displayId = taskInfo.displayId;
+ // Assumes it's safe to show starting windows of launched apps while
+ // the keyguard is being hidden. This is okay because starting windows never show
+ // secret information.
+ // TODO(b/113840485): Occluded may not only happen on default display
+ if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ }
+
+ // Force the window flags: this is a fake window, so it is not really
+ // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
+ // flag because we do know that the next window will take input
+ // focus, so we want to get the IME window up on top of us right away.
+ // Touches will only pass through to the host activity window and will be blocked from
+ // passing to any other windows.
+ windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ params.flags = windowFlags;
+ params.token = appToken;
+ params.packageName = activityInfo.packageName;
+ params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
+ if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ }
+
+ params.setTitle("Splash Screen " + title);
+ return params;
+ }
+ /**
* Create a SplashScreenView object.
*
* In order to speed up the splash screen view to show on first frame, preparing the
@@ -248,6 +393,26 @@
return null;
}
+ /**
+ * Creates a SplashScreenView without read animatable icon and branding image.
+ */
+ SplashScreenView makeSimpleSplashScreenContentView(Context context,
+ StartingWindowInfo info, int themeBGColor) {
+ updateDensity();
+ mTmpAttrs.reset();
+ final ActivityInfo ai = info.targetActivityInfo != null
+ ? info.targetActivityInfo
+ : info.taskInfo.topActivityInfo;
+
+ final SplashViewBuilder builder = new SplashViewBuilder(context, ai);
+ final SplashScreenView view = builder
+ .setWindowBGColor(themeBGColor)
+ .chooseStyle(STARTING_WINDOW_TYPE_SPLASH_SCREEN)
+ .build();
+ view.setNotCopyable();
+ return view;
+ }
+
private SplashScreenView makeSplashScreenContentView(Context context, StartingWindowInfo info,
@StartingWindowType int suggestType, Consumer<Runnable> uiThreadInitConsumer) {
updateDensity();
@@ -263,7 +428,8 @@
final int themeBGColor = legacyDrawable != null
? getBGColorFromCache(ai, () -> estimateWindowBGColor(legacyDrawable))
: getBGColorFromCache(ai, () -> peekWindowBGColor(context, mTmpAttrs));
- return new StartingWindowViewBuilder(context, ai)
+
+ return new SplashViewBuilder(context, ai)
.setWindowBGColor(themeBGColor)
.overlayDrawable(legacyDrawable)
.chooseStyle(suggestType)
@@ -322,6 +488,14 @@
private Drawable mSplashScreenIcon = null;
private Drawable mBrandingImage = null;
private int mIconBgColor = Color.TRANSPARENT;
+
+ void reset() {
+ mWindowBgResId = 0;
+ mWindowBgColor = Color.TRANSPARENT;
+ mSplashScreenIcon = null;
+ mBrandingImage = null;
+ mIconBgColor = Color.TRANSPARENT;
+ }
}
/**
@@ -351,7 +525,7 @@
return appReadyDuration;
}
- private class StartingWindowViewBuilder {
+ private class SplashViewBuilder {
private final Context mContext;
private final ActivityInfo mActivityInfo;
@@ -364,27 +538,28 @@
/** @see #setAllowHandleSolidColor(boolean) **/
private boolean mAllowHandleSolidColor;
- StartingWindowViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
+ SplashViewBuilder(@NonNull Context context, @NonNull ActivityInfo aInfo) {
mContext = context;
mActivityInfo = aInfo;
}
- StartingWindowViewBuilder setWindowBGColor(@ColorInt int background) {
+ SplashViewBuilder setWindowBGColor(@ColorInt int background) {
mThemeColor = background;
return this;
}
- StartingWindowViewBuilder overlayDrawable(Drawable overlay) {
+ SplashViewBuilder overlayDrawable(Drawable overlay) {
mOverlayDrawable = overlay;
return this;
}
- StartingWindowViewBuilder chooseStyle(int suggestType) {
+ SplashViewBuilder chooseStyle(int suggestType) {
mSuggestType = suggestType;
return this;
}
- StartingWindowViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
+ // Set up the UI thread for the View.
+ SplashViewBuilder setUiThreadInitConsumer(Consumer<Runnable> uiThreadInitTask) {
mUiThreadInitTask = uiThreadInitTask;
return this;
}
@@ -395,7 +570,7 @@
* android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)}
* callback, effectively copying the {@link SplashScreenView} into the client process.
*/
- StartingWindowViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
+ SplashViewBuilder setAllowHandleSolidColor(boolean allowHandleSolidColor) {
mAllowHandleSolidColor = allowHandleSolidColor;
return this;
}
@@ -993,10 +1168,11 @@
* Create and play the default exit animation for splash screen view.
*/
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
- Rect frame, Runnable finishCallback, long createTime) {
+ Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
final Runnable playAnimation = () -> {
final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
- view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+ view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+ roundedCornerRadius);
animation.startAnimations();
};
if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
index 7f6bfd2..e419462 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java
@@ -62,7 +62,7 @@
*/
static Drawable[] makeIconDrawable(@ColorInt int backgroundColor, @ColorInt int themeColor,
@NonNull Drawable foregroundDrawable, int srcIconSize, int iconSize,
- boolean loadInDetail, Handler splashscreenWorkerHandler) {
+ boolean loadInDetail, Handler preDrawHandler) {
Drawable foreground;
Drawable background = null;
boolean drawBackground =
@@ -74,13 +74,13 @@
// If the icon is Adaptive, we already use the icon background.
drawBackground = false;
foreground = new ImmobileIconDrawable(foregroundDrawable,
- srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, preDrawHandler);
} else {
// Adaptive icon don't handle transparency so we draw the background of the adaptive
// icon with the same color as the window background color instead of using two layers
foreground = new ImmobileIconDrawable(
new AdaptiveForegroundDrawable(foregroundDrawable),
- srcIconSize, iconSize, loadInDetail, splashscreenWorkerHandler);
+ srcIconSize, iconSize, loadInDetail, preDrawHandler);
}
if (drawBackground) {
@@ -91,9 +91,9 @@
}
static Drawable[] makeLegacyIconDrawable(@NonNull Drawable iconDrawable, int srcIconSize,
- int iconSize, boolean loadInDetail, Handler splashscreenWorkerHandler) {
+ int iconSize, boolean loadInDetail, Handler preDrawHandler) {
return new Drawable[]{new ImmobileIconDrawable(iconDrawable, srcIconSize, iconSize,
- loadInDetail, splashscreenWorkerHandler)};
+ loadInDetail, preDrawHandler)};
}
/**
@@ -107,14 +107,14 @@
private Bitmap mIconBitmap;
ImmobileIconDrawable(Drawable drawable, int srcIconSize, int iconSize, boolean loadInDetail,
- Handler splashscreenWorkerHandler) {
+ Handler preDrawHandler) {
// This icon has lower density, don't scale it.
if (loadInDetail) {
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, iconSize));
+ preDrawHandler.post(() -> preDrawIcon(drawable, iconSize));
} else {
final float scale = (float) iconSize / srcIconSize;
mMatrix.setScale(scale, scale);
- splashscreenWorkerHandler.post(() -> preDrawIcon(drawable, srcIconSize));
+ preDrawHandler.post(() -> preDrawIcon(drawable, srcIconSize));
}
}
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 ff6f2b0..4f07bfe 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.startingsurface;
-import static android.content.Context.CONTEXT_RESTRICTED;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -32,8 +31,6 @@
import android.content.pm.ActivityInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PixelFormat;
@@ -198,118 +195,21 @@
if (activityInfo == null || activityInfo.packageName == null) {
return;
}
-
- final int displayId = taskInfo.displayId;
- final int taskId = taskInfo.taskId;
-
// replace with the default theme if the application didn't set
final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen for package: %s with theme: %s for task: %d, suggestType: %d",
- activityInfo.packageName, Integer.toHexString(theme), taskId, suggestType);
- final Display display = getDisplay(displayId);
- if (display == null) {
- // Can't show splash screen on requested display, so skip showing at all.
- return;
- }
- Context context = displayId == DEFAULT_DISPLAY
- ? mContext : mContext.createDisplayContext(display);
+ final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+ suggestType, mDisplayManager);
if (context == null) {
return;
}
- if (theme != context.getThemeResId()) {
- try {
- context = context.createPackageContextAsUser(activityInfo.packageName,
- CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
- context.setTheme(theme);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Failed creating package context with package name "
- + activityInfo.packageName + " for user " + taskInfo.userId, e);
- return;
- }
- }
+ final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+ context, windowInfo, suggestType, activityInfo.packageName,
+ suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
- final Configuration taskConfig = taskInfo.getConfiguration();
- if (taskConfig.diffPublicOnly(context.getResources().getConfiguration()) != 0) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen: creating context based on task Configuration %s",
- taskConfig);
- final Context overrideContext = context.createConfigurationContext(taskConfig);
- overrideContext.setTheme(theme);
- final TypedArray typedArray = overrideContext.obtainStyledAttributes(
- com.android.internal.R.styleable.Window);
- final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
- try {
- if (resId != 0 && overrideContext.getDrawable(resId) != null) {
- // We want to use the windowBackground for the override context if it is
- // available, otherwise we use the default one to make sure a themed starting
- // window is displayed for the app.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "addSplashScreen: apply overrideConfig %s",
- taskConfig);
- context = overrideContext;
- }
- } catch (Resources.NotFoundException e) {
- Slog.w(TAG, "failed creating starting window for overrideConfig at taskId: "
- + taskId, e);
- return;
- }
- typedArray.recycle();
- }
-
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
- params.setFitInsetsSides(0);
- params.setFitInsetsTypes(0);
- params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
- ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
- int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
- final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
- if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
- windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
- }
- if (suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
- if (a.getBoolean(R.styleable.Window_windowDrawsSystemBarBackgrounds, false)) {
- windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- } else {
- windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- }
- params.layoutInDisplayCutoutMode = a.getInt(
- R.styleable.Window_windowLayoutInDisplayCutoutMode,
- params.layoutInDisplayCutoutMode);
- params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
- a.recycle();
-
- // Assumes it's safe to show starting windows of launched apps while
- // the keyguard is being hidden. This is okay because starting windows never show
- // secret information.
- // TODO(b/113840485): Occluded may not only happen on default display
- if (displayId == DEFAULT_DISPLAY && windowInfo.isKeyguardOccluded) {
- windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
- }
-
- // Force the window flags: this is a fake window, so it is not really
- // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
- // flag because we do know that the next window will take input
- // focus, so we want to get the IME window up on top of us right away.
- // Touches will only pass through to the host activity window and will be blocked from
- // passing to any other windows.
- windowFlags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
- params.flags = windowFlags;
- params.token = appToken;
- params.packageName = activityInfo.packageName;
- params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-
- if (!context.getResources().getCompatibilityInfo().supportsScreen()) {
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
- }
-
- params.setTitle("Splash Screen " + activityInfo.packageName);
+ final int displayId = taskInfo.displayId;
+ final int taskId = taskInfo.taskId;
+ final Display display = getDisplay(displayId);
// TODO(b/173975965) tracking performance
// Prepare the splash screen content view on splash screen worker thread in parallel, so the
@@ -646,7 +546,7 @@
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime);
+ record.mCreateTime, removalInfo.roundedCornerRadius);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 3929e83..9d6711f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -18,50 +18,16 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
-import static android.graphics.Color.alpha;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
-import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SCALED;
-import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
-
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
-import android.app.ActivityThread;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.GraphicBuffer;
-import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -73,20 +39,14 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
+import android.window.SnapshotDrawerUtils;
import android.window.StartingWindowInfo;
import android.window.TaskSnapshot;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DecorView;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.view.BaseIWindow;
import com.android.wm.shell.common.ShellExecutor;
@@ -100,27 +60,8 @@
* @hide
*/
public class TaskSnapshotWindow {
- /**
- * When creating the starting window, we use the exact same layout flags such that we end up
- * with a window with the exact same dimensions etc. However, these flags are not used in layout
- * and might cause other side effects so we exclude them.
- */
- static final int FLAG_INHERIT_EXCLUDES = FLAG_NOT_FOCUSABLE
- | FLAG_NOT_TOUCHABLE
- | FLAG_NOT_TOUCH_MODAL
- | FLAG_ALT_FOCUSABLE_IM
- | FLAG_NOT_FOCUSABLE
- | FLAG_HARDWARE_ACCELERATED
- | FLAG_IGNORE_CHEEK_PRESSES
- | FLAG_LOCAL_FOCUS_MODE
- | FLAG_SLIPPERY
- | FLAG_WATCH_OUTSIDE_TOUCH
- | FLAG_SPLIT_TOUCH
- | FLAG_SCALED
- | FLAG_SECURE;
-
private static final String TAG = StartingWindowController.TAG;
- private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
+ private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
/**
@@ -133,25 +74,12 @@
private final Window mWindow;
private final Runnable mClearWindowHandler;
private final ShellExecutor mSplashScreenExecutor;
- private final SurfaceControl mSurfaceControl;
private final IWindowSession mSession;
- private final Rect mTaskBounds;
- private final Rect mFrame = new Rect();
- private final Rect mSystemBarInsets = new Rect();
- private TaskSnapshot mSnapshot;
- private final RectF mTmpSnapshotSize = new RectF();
- private final RectF mTmpDstFrame = new RectF();
- private final CharSequence mTitle;
private boolean mHasDrawn;
- private boolean mSizeMismatch;
private final Paint mBackgroundPaint = new Paint();
private final int mActivityType;
- private final int mStatusBarColor;
- private final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
private final int mOrientationOnCreation;
- private final SurfaceControl.Transaction mTransaction;
- private final Matrix mSnapshotMatrix = new Matrix();
- private final float[] mTmpFloat9 = new float[9];
+
private final Runnable mScheduledRunnable = this::removeImmediately;
private final boolean mHasImeSurface;
@@ -163,42 +91,15 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
"create taskSnapshot surface for task: %d", taskId);
- final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
- final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams;
final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
- if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) {
- Slog.w(TAG, "unable to create taskSnapshot surface for task: " + taskId);
+
+ final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters(
+ info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING,
+ snapshot.getHardwareBuffer().getFormat(), appToken);
+ if (layoutParams == null) {
+ Slog.e(TAG, "TaskSnapshotWindow no layoutParams");
return null;
}
- final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
-
- final int appearance = attrs.insetsFlags.appearance;
- final int windowFlags = attrs.flags;
- final int windowPrivateFlags = attrs.privateFlags;
-
- layoutParams.packageName = mainWindowParams.packageName;
- layoutParams.windowAnimations = mainWindowParams.windowAnimations;
- layoutParams.dimAmount = mainWindowParams.dimAmount;
- layoutParams.type = TYPE_APPLICATION_STARTING;
- layoutParams.format = snapshot.getHardwareBuffer().getFormat();
- layoutParams.flags = (windowFlags & ~FLAG_INHERIT_EXCLUDES)
- | FLAG_NOT_FOCUSABLE
- | FLAG_NOT_TOUCHABLE;
- // Setting as trusted overlay to let touches pass through. This is safe because this
- // window is controlled by the system.
- layoutParams.privateFlags = (windowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS)
- | PRIVATE_FLAG_TRUSTED_OVERLAY | PRIVATE_FLAG_USE_BLAST;
- layoutParams.token = appToken;
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.insetsFlags.appearance = appearance;
- layoutParams.insetsFlags.behavior = attrs.insetsFlags.behavior;
- layoutParams.layoutInDisplayCutoutMode = attrs.layoutInDisplayCutoutMode;
- layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
- layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
- layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
-
- layoutParams.setTitle(String.format(TITLE_FORMAT, taskId));
final Point taskSize = snapshot.getTaskSize();
final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
@@ -222,9 +123,8 @@
}
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
- surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, appearance,
- windowFlags, windowPrivateFlags, taskBounds, orientation, activityType,
- info.requestedVisibleTypes, clearWindowHandler, splashScreenExecutor);
+ snapshot, taskDescription, orientation, activityType,
+ clearWindowHandler, splashScreenExecutor);
final Window window = snapshotSurface.mWindow;
final InsetsState tmpInsetsState = new InsetsState();
@@ -255,33 +155,25 @@
snapshotSurface.clearWindowSynced();
}
- final Rect systemBarInsets = getSystemBarInsets(tmpFrames.frame, topWindowInsetsState);
- snapshotSurface.setFrames(tmpFrames.frame, systemBarInsets);
- snapshotSurface.drawSnapshot();
+ SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
+ taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */);
+ snapshotSurface.mHasDrawn = true;
+ snapshotSurface.reportDrawn();
+
return snapshotSurface;
}
- public TaskSnapshotWindow(SurfaceControl surfaceControl,
- TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
- int appearance, int windowFlags, int windowPrivateFlags, Rect taskBounds,
- int currentOrientation, int activityType, @InsetsType int requestedVisibleTypes,
- Runnable clearWindowHandler, ShellExecutor splashScreenExecutor) {
+ public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
+ int currentOrientation, int activityType, Runnable clearWindowHandler,
+ ShellExecutor splashScreenExecutor) {
mSplashScreenExecutor = splashScreenExecutor;
mSession = WindowManagerGlobal.getWindowSession();
mWindow = new Window();
mWindow.setSession(mSession);
- mSurfaceControl = surfaceControl;
- mSnapshot = snapshot;
- mTitle = title;
int backgroundColor = taskDescription.getBackgroundColor();
mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
- mTaskBounds = taskBounds;
- mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
- windowPrivateFlags, appearance, taskDescription, 1f, requestedVisibleTypes);
- mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
mActivityType = activityType;
- mTransaction = new SurfaceControl.Transaction();
mClearWindowHandler = clearWindowHandler;
mHasImeSurface = snapshot.hasImeSurface();
}
@@ -294,23 +186,6 @@
return mHasImeSurface;
}
- /**
- * Ask system bar background painter to draw status bar background.
- * @hide
- */
- public void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame) {
- mSystemBarBackgroundPainter.drawStatusBarBackground(c, alreadyDrawnFrame,
- mSystemBarBackgroundPainter.getStatusBarColorViewHeight());
- }
-
- /**
- * Ask system bar background painter to draw navigation bar background.
- * @hide
- */
- public void drawNavigationBarBackground(Canvas c) {
- mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
- }
-
void scheduleRemove(boolean deferRemoveForIme) {
// Show the latest content as soon as possible for unlocking to home.
if (mActivityType == ACTIVITY_TYPE_HOME) {
@@ -338,178 +213,6 @@
}
/**
- * Set frame size.
- * @hide
- */
- public void setFrames(Rect frame, Rect systemBarInsets) {
- mFrame.set(frame);
- mSystemBarInsets.set(systemBarInsets);
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- mSizeMismatch = (mFrame.width() != snapshot.getWidth()
- || mFrame.height() != snapshot.getHeight());
- mSystemBarBackgroundPainter.setInsets(systemBarInsets);
- }
-
- static Rect getSystemBarInsets(Rect frame, InsetsState state) {
- return state.calculateInsets(frame, WindowInsets.Type.systemBars(),
- false /* ignoreVisibility */).toRect();
- }
-
- private void drawSnapshot() {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
- "Drawing snapshot surface sizeMismatch=%b", mSizeMismatch);
- if (mSizeMismatch) {
- // The dimensions of the buffer and the window don't match, so attaching the buffer
- // will fail. Better create a child window with the exact dimensions and fill the parent
- // window with the background color!
- drawSizeMismatchSnapshot();
- } else {
- drawSizeMatchSnapshot();
- }
- mHasDrawn = true;
- reportDrawn();
-
- // In case window manager leaks us, make sure we don't retain the snapshot.
- if (mSnapshot.getHardwareBuffer() != null) {
- mSnapshot.getHardwareBuffer().close();
- }
- mSnapshot = null;
- mSurfaceControl.release();
- }
-
- private void drawSizeMatchSnapshot() {
- mTransaction.setBuffer(mSurfaceControl, mSnapshot.getHardwareBuffer())
- .setColorSpace(mSurfaceControl, mSnapshot.getColorSpace())
- .apply();
- }
-
- private void drawSizeMismatchSnapshot() {
- final HardwareBuffer buffer = mSnapshot.getHardwareBuffer();
- final SurfaceSession session = new SurfaceSession();
-
- // We consider nearly matched dimensions as there can be rounding errors and the user won't
- // notice very minute differences from scaling one dimension more than the other
- final boolean aspectRatioMismatch = Math.abs(
- ((float) buffer.getWidth() / buffer.getHeight())
- - ((float) mFrame.width() / mFrame.height())) > 0.01f;
-
- // Keep a reference to it such that it doesn't get destroyed when finalized.
- SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session)
- .setName(mTitle + " - task-snapshot-surface")
- .setBLASTLayer()
- .setFormat(buffer.getFormat())
- .setParent(mSurfaceControl)
- .setCallsite("TaskSnapshotWindow.drawSizeMismatchSnapshot")
- .build();
-
- final Rect frame;
- // We can just show the surface here as it will still be hidden as the parent is
- // still hidden.
- mTransaction.show(childSurfaceControl);
- if (aspectRatioMismatch) {
- // Clip off ugly navigation bar.
- final Rect crop = calculateSnapshotCrop();
- frame = calculateSnapshotFrame(crop);
- mTransaction.setWindowCrop(childSurfaceControl, crop);
- mTransaction.setPosition(childSurfaceControl, frame.left, frame.top);
- mTmpSnapshotSize.set(crop);
- mTmpDstFrame.set(frame);
- } else {
- frame = null;
- mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
- mTmpDstFrame.set(mFrame);
- mTmpDstFrame.offsetTo(0, 0);
- }
-
- // Scale the mismatch dimensions to fill the task bounds
- mSnapshotMatrix.setRectToRect(mTmpSnapshotSize, mTmpDstFrame, Matrix.ScaleToFit.FILL);
- mTransaction.setMatrix(childSurfaceControl, mSnapshotMatrix, mTmpFloat9);
- mTransaction.setColorSpace(childSurfaceControl, mSnapshot.getColorSpace());
- mTransaction.setBuffer(childSurfaceControl, mSnapshot.getHardwareBuffer());
-
- if (aspectRatioMismatch) {
- GraphicBuffer background = GraphicBuffer.create(mFrame.width(), mFrame.height(),
- PixelFormat.RGBA_8888,
- GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
- | GraphicBuffer.USAGE_SW_WRITE_RARELY);
- // TODO: Support this on HardwareBuffer
- final Canvas c = background.lockCanvas();
- drawBackgroundAndBars(c, frame);
- background.unlockCanvasAndPost(c);
- mTransaction.setBuffer(mSurfaceControl,
- HardwareBuffer.createFromGraphicBuffer(background));
- }
- mTransaction.apply();
- childSurfaceControl.release();
- }
-
- /**
- * Calculates the snapshot crop in snapshot coordinate space.
- *
- * @return crop rect in snapshot coordinate space.
- */
- public Rect calculateSnapshotCrop() {
- final Rect rect = new Rect();
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- rect.set(0, 0, snapshot.getWidth(), snapshot.getHeight());
- final Rect insets = mSnapshot.getContentInsets();
-
- final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
- final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
- // Let's remove all system decorations except the status bar, but only if the task is at the
- // very top of the screen.
- final boolean isTop = mTaskBounds.top == 0 && mFrame.top == 0;
- rect.inset((int) (insets.left * scaleX),
- isTop ? 0 : (int) (insets.top * scaleY),
- (int) (insets.right * scaleX),
- (int) (insets.bottom * scaleY));
- return rect;
- }
-
- /**
- * Calculates the snapshot frame in window coordinate space from crop.
- *
- * @param crop rect that is in snapshot coordinate space.
- */
- public Rect calculateSnapshotFrame(Rect crop) {
- final HardwareBuffer snapshot = mSnapshot.getHardwareBuffer();
- final float scaleX = (float) snapshot.getWidth() / mSnapshot.getTaskSize().x;
- final float scaleY = (float) snapshot.getHeight() / mSnapshot.getTaskSize().y;
-
- // Rescale the frame from snapshot to window coordinate space
- final Rect frame = new Rect(0, 0,
- (int) (crop.width() / scaleX + 0.5f),
- (int) (crop.height() / scaleY + 0.5f)
- );
-
- // However, we also need to make space for the navigation bar on the left side.
- frame.offset(mSystemBarInsets.left, 0);
- return frame;
- }
-
- /**
- * Draw status bar and navigation bar background.
- * @hide
- */
- public void drawBackgroundAndBars(Canvas c, Rect frame) {
- final int statusBarHeight = mSystemBarBackgroundPainter.getStatusBarColorViewHeight();
- final boolean fillHorizontally = c.getWidth() > frame.right;
- final boolean fillVertically = c.getHeight() > frame.bottom;
- if (fillHorizontally) {
- c.drawRect(frame.right, alpha(mStatusBarColor) == 0xFF ? statusBarHeight : 0,
- c.getWidth(), fillVertically
- ? frame.bottom
- : c.getHeight(),
- mBackgroundPaint);
- }
- if (fillVertically) {
- c.drawRect(0, frame.bottom, c.getWidth(), c.getHeight(), mBackgroundPaint);
- }
- mSystemBarBackgroundPainter.drawDecors(c, frame);
- }
-
- /**
* Clear window from drawer, must be post on main executor.
*/
private void clearWindowSynced() {
@@ -557,92 +260,4 @@
});
}
}
-
- /**
- * Helper class to draw the background of the system bars in regions the task snapshot isn't
- * filling the window.
- */
- static class SystemBarBackgroundPainter {
- private final Paint mStatusBarPaint = new Paint();
- private final Paint mNavigationBarPaint = new Paint();
- private final int mStatusBarColor;
- private final int mNavigationBarColor;
- private final int mWindowFlags;
- private final int mWindowPrivateFlags;
- private final float mScale;
- private final @InsetsType int mRequestedVisibleTypes;
- private final Rect mSystemBarInsets = new Rect();
-
- SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- TaskDescription taskDescription, float scale,
- @InsetsType int requestedVisibleTypes) {
- mWindowFlags = windowFlags;
- mWindowPrivateFlags = windowPrivateFlags;
- mScale = scale;
- final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
- final int semiTransparent = context.getColor(
- R.color.system_bar_background_semi_transparent);
- mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
- semiTransparent, taskDescription.getStatusBarColor(), appearance,
- APPEARANCE_LIGHT_STATUS_BARS,
- taskDescription.getEnsureStatusBarContrastWhenTransparent());
- mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
- FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
- taskDescription.getNavigationBarColor(), appearance,
- APPEARANCE_LIGHT_NAVIGATION_BARS,
- taskDescription.getEnsureNavigationBarContrastWhenTransparent()
- && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
- mStatusBarPaint.setColor(mStatusBarColor);
- mNavigationBarPaint.setColor(mNavigationBarColor);
- mRequestedVisibleTypes = requestedVisibleTypes;
- }
-
- void setInsets(Rect systemBarInsets) {
- mSystemBarInsets.set(systemBarInsets);
- }
-
- int getStatusBarColorViewHeight() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
- return (int) (mSystemBarInsets.top * mScale);
- } else {
- return 0;
- }
- }
-
- private boolean isNavigationBarColorViewVisible() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
- }
-
- void drawDecors(Canvas c, @Nullable Rect alreadyDrawnFrame) {
- drawStatusBarBackground(c, alreadyDrawnFrame, getStatusBarColorViewHeight());
- drawNavigationBarBackground(c);
- }
-
- void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame,
- int statusBarHeight) {
- if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0
- && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) {
- final int rightInset = (int) (mSystemBarInsets.right * mScale);
- final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0;
- c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
- }
- }
-
- @VisibleForTesting
- void drawNavigationBarBackground(Canvas c) {
- final Rect navigationBarRect = new Rect();
- getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
- mScale);
- final boolean visible = isNavigationBarColorViewVisible();
- if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
- c.drawRect(navigationBarRect, mNavigationBarPaint);
- }
- }
- }
}
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 e40db4e..5655402 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
@@ -55,6 +55,7 @@
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
+import java.util.Optional;
import java.util.function.Supplier;
/**
@@ -74,11 +75,11 @@
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
- private DesktopModeController mDesktopModeController;
- private EventReceiver mEventReceiver;
- private InputMonitor mInputMonitor;
+ private Optional<DesktopModeController> mDesktopModeController;
private boolean mTransitionDragActive;
+ private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
+
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private EventReceiverFactory mEventReceiverFactory = new EventReceiverFactory();
@@ -90,7 +91,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- DesktopModeController desktopModeController) {
+ Optional<DesktopModeController> desktopModeController) {
this(
context,
mainHandler,
@@ -110,7 +111,7 @@
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
SyncTransactionQueue syncQueue,
- DesktopModeController desktopModeController,
+ Optional<DesktopModeController> desktopModeController,
CaptionWindowDecoration.Factory captionWindowDecorFactory,
Supplier<InputManager> inputManagerSupplier) {
@@ -150,8 +151,15 @@
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+
if (decoration == null) return;
+ int oldDisplayId = decoration.mDisplay.getDisplayId();
+ if (taskInfo.displayId != oldDisplayId) {
+ removeTaskFromEventReceiver(oldDisplayId);
+ incrementEventReceiverTasks(taskInfo.displayId);
+ }
+
decoration.relayout(taskInfo);
}
@@ -195,6 +203,11 @@
if (decoration == null) return;
decoration.close();
+ int displayId = taskInfo.displayId;
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ removeTaskFromEventReceiver(displayId);
+ }
}
private class CaptionTouchEventListener implements
@@ -234,10 +247,10 @@
} else if (id == R.id.caption_handle) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
- mDesktopModeController.setDesktopModeActive(true);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
- mDesktopModeController.setDesktopModeActive(false);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
decoration.closeHandleMenu();
decoration.setButtonVisibility();
}
@@ -292,9 +305,9 @@
*/
private void handleEventForMove(MotionEvent e) {
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- int windowingMode = mDesktopModeController
- .getDisplayAreaWindowingMode(taskInfo.displayId);
- if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (mDesktopModeController.isPresent()
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
+ == WINDOWING_MODE_FULLSCREEN) {
return;
}
switch (e.getActionMasked()) {
@@ -319,7 +332,7 @@
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight
&& DesktopModeStatus.isActive(mContext)) {
- mDesktopModeController.setDesktopModeActive(false);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
}
break;
}
@@ -329,8 +342,12 @@
// InputEventReceiver to listen for touch input outside of caption bounds
class EventReceiver extends InputEventReceiver {
- EventReceiver(InputChannel channel, Looper looper) {
+ private InputMonitor mInputMonitor;
+ private int mTasksOnDisplay;
+ EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
super(channel, looper);
+ mInputMonitor = inputMonitor;
+ mTasksOnDisplay = 1;
}
@Override
@@ -338,15 +355,62 @@
boolean handled = false;
if (event instanceof MotionEvent) {
handled = true;
- CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
+ CaptionWindowDecorViewModel.this
+ .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
}
finishInputEvent(event, handled);
}
+
+ @Override
+ public void dispose() {
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ super.dispose();
+ }
+
+ private void incrementTaskNumber() {
+ mTasksOnDisplay++;
+ }
+
+ private void decrementTaskNumber() {
+ mTasksOnDisplay--;
+ }
+
+ private int getTasksOnDisplay() {
+ return mTasksOnDisplay;
+ }
+ }
+
+ /**
+ * Check if an EventReceiver exists on a particular display.
+ * If it does, increment its task count. Otherwise, create one for that display.
+ * @param displayId the display to check against
+ */
+ private void incrementEventReceiverTasks(int displayId) {
+ if (mEventReceiversByDisplay.contains(displayId)) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ eventReceiver.incrementTaskNumber();
+ } else {
+ createInputChannel(displayId);
+ }
+ }
+
+ // If all tasks on this display are gone, we don't need to monitor its input.
+ private void removeTaskFromEventReceiver(int displayId) {
+ if (!mEventReceiversByDisplay.contains(displayId)) return;
+ EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
+ if (eventReceiver == null) return;
+ eventReceiver.decrementTaskNumber();
+ if (eventReceiver.getTasksOnDisplay() == 0) {
+ disposeInputChannel(displayId);
+ }
}
class EventReceiverFactory {
- EventReceiver create(InputChannel channel, Looper looper) {
- return new EventReceiver(channel, looper);
+ EventReceiver create(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
+ return new EventReceiver(inputMonitor, channel, looper);
}
}
@@ -355,14 +419,14 @@
*
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
*/
- private void handleReceivedMotionEvent(MotionEvent ev) {
+ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
if (!DesktopModeStatus.isActive(mContext)) {
handleCaptionThroughStatusBar(ev);
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
- mInputMonitor.pilferPointers();
+ inputMonitor.pilferPointers();
}
}
@@ -381,6 +445,7 @@
}
}
+
/**
* Perform caption actions if not able to through normal means.
* Turn on desktop mode if handle is dragged below status bar.
@@ -407,7 +472,7 @@
int statusBarHeight = mDisplayController
.getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
- mDesktopModeController.setDesktopModeActive(true);
+ mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
return;
}
}
@@ -434,6 +499,22 @@
return focusedDecor;
}
+ private void createInputChannel(int displayId) {
+ InputManager inputManager = mInputManagerSupplier.get();
+ InputMonitor inputMonitor =
+ inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
+ EventReceiver eventReceiver = mEventReceiverFactory.create(
+ inputMonitor, inputMonitor.getInputChannel(), Looper.myLooper());
+ mEventReceiversByDisplay.put(displayId, eventReceiver);
+ }
+
+ private void disposeInputChannel(int displayId) {
+ EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
+ if (eventReceiver != null) {
+ eventReceiver.dispose();
+ }
+ }
+
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -472,14 +553,7 @@
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT);
- if (mInputMonitor == null) {
- InputManager inputManager = mInputManagerSupplier.get();
- mInputMonitor =
- inputManager.monitorGestureInput("caption-touch", mContext.getDisplayId());
- mEventReceiver =
- mEventReceiverFactory.create(
- mInputMonitor.getInputChannel(), Looper.myLooper());
- }
+ incrementEventReceiverTasks(taskInfo.displayId);
}
private class DragStartListenerImpl implements TaskPositioner.DragStartListener {
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 08913c6..27fc381a 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -19,6 +19,8 @@
<option name="run-command" value="locksettings set-disabled false" />
<!-- restart launcher to activate TAPL -->
<option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
+ <!-- Ensure output directory is empty at the start -->
+ <option name="run-command" value="rm -rf /sdcard/flicker" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 6370df4..8465678 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -20,10 +20,10 @@
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
@@ -45,12 +45,12 @@
abstract class BaseTest
@JvmOverloads
constructor(
- protected val testSpec: FlickerTestParameter,
+ protected val flicker: FlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
init {
- testSpec.setIsTablet(
+ flicker.scenario.setIsTablet(
WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
.currentState
.wmState
@@ -68,13 +68,13 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+ setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
transition()
}
}
/** Checks that all parts of the screen are covered during the transition */
- @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
/**
* Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
@@ -82,8 +82,8 @@
@Presubmit
@Test
open fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -93,8 +93,8 @@
@Presubmit
@Test
open fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
/**
@@ -105,8 +105,8 @@
@Presubmit
@Test
open fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsAlwaysVisible()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsAlwaysVisible()
}
/**
@@ -115,8 +115,8 @@
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -127,8 +127,8 @@
@Presubmit
@Test
open fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarWindowIsAlwaysVisible()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsAlwaysVisible()
}
/**
@@ -137,8 +137,7 @@
*/
@Presubmit
@Test
- open fun statusBarLayerIsVisibleAtStartAndEnd() =
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
/**
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
@@ -146,7 +145,7 @@
*/
@Presubmit
@Test
- open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
+ open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
/**
* Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
@@ -154,7 +153,7 @@
*/
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
/**
* Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
@@ -163,7 +162,7 @@
@Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
}
/**
@@ -173,6 +172,6 @@
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 8765ad1..5186914 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -18,23 +18,23 @@
package com.android.wm.shell.flicker
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.server.wm.traces.common.IComponentMatcher
import com.android.server.wm.traces.common.region.Region
+import com.android.server.wm.traces.common.service.PlatformConsts
-fun FlickerTestParameter.appPairsDividerIsVisibleAtEnd() {
+fun FlickerTest.appPairsDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsDividerIsInvisibleAtEnd() {
+fun FlickerTest.appPairsDividerIsInvisibleAtEnd() {
assertLayersEnd { this.notContains(APP_PAIR_SPLIT_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsDividerBecomesVisible() {
+fun FlickerTest.appPairsDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -42,7 +42,7 @@
}
}
-fun FlickerTestParameter.splitScreenEntered(
+fun FlickerTest.splitScreenEntered(
component1: IComponentMatcher,
component2: IComponentMatcher,
fromOtherApp: Boolean,
@@ -69,7 +69,7 @@
splitScreenDividerIsVisibleAtEnd()
}
-fun FlickerTestParameter.splitScreenDismissed(
+fun FlickerTest.splitScreenDismissed(
component1: IComponentMatcher,
component2: IComponentMatcher,
toHome: Boolean
@@ -87,27 +87,27 @@
splitScreenDividerIsInvisibleAtEnd()
}
-fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+fun FlickerTest.splitScreenDividerIsVisibleAtStart() {
assertLayersStart { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+fun FlickerTest.splitScreenDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+fun FlickerTest.splitScreenDividerIsInvisibleAtStart() {
assertLayersStart { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+fun FlickerTest.splitScreenDividerIsInvisibleAtEnd() {
assertLayersEnd { this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
+fun FlickerTest.splitScreenDividerBecomesVisible() {
layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
-fun FlickerTestParameter.splitScreenDividerBecomesInvisible() {
+fun FlickerTest.splitScreenDividerBecomesInvisible() {
assertLayers {
this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
@@ -115,23 +115,23 @@
}
}
-fun FlickerTestParameter.layerBecomesVisible(component: IComponentMatcher) {
+fun FlickerTest.layerBecomesVisible(component: IComponentMatcher) {
assertLayers { this.isInvisible(component).then().isVisible(component) }
}
-fun FlickerTestParameter.layerBecomesInvisible(component: IComponentMatcher) {
+fun FlickerTest.layerBecomesInvisible(component: IComponentMatcher) {
assertLayers { this.isVisible(component).then().isInvisible(component) }
}
-fun FlickerTestParameter.layerIsVisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.layerIsVisibleAtEnd(component: IComponentMatcher) {
assertLayersEnd { this.isVisible(component) }
}
-fun FlickerTestParameter.layerKeepVisible(component: IComponentMatcher) {
+fun FlickerTest.layerKeepVisible(component: IComponentMatcher) {
assertLayers { this.isVisible(component) }
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
+fun FlickerTest.splitAppLayerBoundsBecomesVisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -145,12 +145,12 @@
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
}
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
+fun FlickerTest.splitAppLayerBoundsBecomesVisibleByDrag(component: IComponentMatcher) {
assertLayers {
this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
@@ -161,7 +161,7 @@
}
}
-fun FlickerTestParameter.splitAppLayerBoundsBecomesInvisible(
+fun FlickerTest.splitAppLayerBoundsBecomesInvisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -171,7 +171,7 @@
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
.then()
.isVisible(component, true)
@@ -180,27 +180,37 @@
}
}
-fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
+fun FlickerTest.splitAppLayerBoundsIsVisibleAtEnd(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
) {
assertLayersEnd {
- splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
}
}
-fun FlickerTestParameter.splitAppLayerBoundsKeepVisible(
+fun FlickerTest.splitAppLayerBoundsKeepVisible(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
) {
assertLayers {
- splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, endRotation)
+ splitAppLayerBoundsSnapToDivider(
+ component,
+ landscapePosLeft,
+ portraitPosTop,
+ scenario.endRotation
+ )
}
}
-fun FlickerTestParameter.splitAppLayerBoundsChanges(
+fun FlickerTest.splitAppLayerBoundsChanges(
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean
@@ -211,14 +221,14 @@
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
} else {
this.splitAppLayerBoundsSnapToDivider(
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
.then()
.isInvisible(component)
@@ -227,7 +237,7 @@
component,
landscapePosLeft,
portraitPosTop,
- endRotation
+ scenario.endRotation
)
}
}
@@ -237,7 +247,7 @@
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean,
- rotation: Int
+ rotation: PlatformConsts.Rotation
): LayersTraceSubject {
return invoke("splitAppLayerBoundsSnapToDivider") {
it.splitAppLayerBoundsSnapToDivider(component, landscapePosLeft, portraitPosTop, rotation)
@@ -248,7 +258,7 @@
component: IComponentMatcher,
landscapePosLeft: Boolean,
portraitPosTop: Boolean,
- rotation: Int
+ rotation: PlatformConsts.Rotation
): LayerTraceEntrySubject {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return invoke {
@@ -292,7 +302,7 @@
}
}
-fun FlickerTestParameter.appWindowBecomesVisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowBecomesVisible(component: IComponentMatcher) {
assertWm {
this.isAppWindowInvisible(component)
.then()
@@ -304,39 +314,39 @@
}
}
-fun FlickerTestParameter.appWindowBecomesInvisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowBecomesInvisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component).then().isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.appWindowIsVisibleAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsVisibleAtStart(component: IComponentMatcher) {
assertWmStart { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsVisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsInvisibleAtStart(component: IComponentMatcher) {
assertWmStart { this.isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsInvisibleAtEnd(component: IComponentMatcher) {
assertWmEnd { this.isAppWindowInvisible(component) }
}
-fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+fun FlickerTest.appWindowIsNotContainAtStart(component: IComponentMatcher) {
assertWmStart { this.notContains(component) }
}
-fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
+fun FlickerTest.appWindowKeepVisible(component: IComponentMatcher) {
assertWm { this.isAppWindowVisible(component) }
}
-fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
+fun FlickerTest.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.dockedStackDividerBecomesVisible() {
+fun FlickerTest.dockedStackDividerBecomesVisible() {
assertLayers {
this.isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -344,7 +354,7 @@
}
}
-fun FlickerTestParameter.dockedStackDividerBecomesInvisible() {
+fun FlickerTest.dockedStackDividerBecomesInvisible() {
assertLayers {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
.then()
@@ -352,12 +362,12 @@
}
}
-fun FlickerTestParameter.dockedStackDividerNotExistsAtEnd() {
+fun FlickerTest.dockedStackDividerNotExistsAtEnd() {
assertLayersEnd { this.notContains(DOCKED_STACK_DIVIDER_COMPONENT) }
}
-fun FlickerTestParameter.appPairsPrimaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.appPairsPrimaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
primaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -366,8 +376,8 @@
}
}
-fun FlickerTestParameter.dockedStackPrimaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.dockedStackPrimaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
primaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -376,8 +386,8 @@
}
}
-fun FlickerTestParameter.appPairsSecondaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.appPairsSecondaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
secondaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -386,8 +396,8 @@
}
}
-fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
- rotation: Int,
+fun FlickerTest.dockedStackSecondaryBoundsIsVisibleAtEnd(
+ rotation: PlatformConsts.Rotation,
secondaryComponent: IComponentMatcher
) {
assertLayersEnd {
@@ -396,38 +406,38 @@
}
}
-fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getPrimaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(
- 0,
- 0,
- displayBounds.bounds.right,
- dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
- )
- } else {
+ return if (rotation.isRotated()) {
Region.from(
0,
0,
dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
displayBounds.bounds.bottom
)
+ } else {
+ Region.from(
+ 0,
+ 0,
+ displayBounds.bounds.right,
+ dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
+ )
}
}
-fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
+fun getSecondaryRegion(dividerRegion: Region, rotation: PlatformConsts.Rotation): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
- return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
+ return if (rotation.isRotated()) {
Region.from(
+ dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
0,
- dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.bounds.right,
displayBounds.bounds.bottom
)
} else {
Region.from(
- dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset,
0,
+ dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
displayBounds.bounds.right,
displayBounds.bounds.bottom
)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 0fc2004..996b677 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -21,21 +21,21 @@
import android.content.Context
import android.content.pm.PackageManager
import android.os.ServiceManager
-import android.view.Surface
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.Flicker
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.IFlickerTestData
import com.android.server.wm.flicker.helpers.LaunchBubbleHelper
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.BaseTest
import org.junit.runners.Parameterized
/** Base configurations for Bubble flicker tests */
-abstract class BaseBubbleScreen(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class BaseBubbleScreen(flicker: FlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val testApp = LaunchBubbleHelper(instrumentation)
@@ -79,17 +79,18 @@
}
}
- protected fun Flicker.waitAndGetAddBubbleBtn(): UiObject2? =
+ protected fun IFlickerTestData.waitAndGetAddBubbleBtn(): UiObject2? =
device.wait(Until.findObject(By.text("Add Bubble")), FIND_OBJECT_TIMEOUT)
- protected fun Flicker.waitAndGetCancelAllBtn(): UiObject2? =
+ protected fun IFlickerTestData.waitAndGetCancelAllBtn(): UiObject2? =
device.wait(Until.findObject(By.text("Cancel All Bubble")), FIND_OBJECT_TIMEOUT)
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
const val FIND_OBJECT_TIMEOUT = 2000L
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
index ab72117..7fc12f0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/DismissBubbleScreen.kt
@@ -25,9 +25,9 @@
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -45,7 +45,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class DismissBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class DismissBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
private val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
private val displaySize = DisplayMetrics()
@@ -72,7 +72,7 @@
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
/** {@inheritDoc} */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
index 226eab8..0cda626 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt
@@ -20,9 +20,9 @@
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -42,7 +42,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class ExpandBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -64,6 +64,6 @@
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 47167b8..04b1bdd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -23,9 +23,9 @@
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -43,7 +43,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+class LaunchBubbleFromLockScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -88,7 +88,7 @@
@FlakyTest(bugId = 242088970)
@Test
fun testAppIsVisibleAtEnd() {
- testSpec.assertLayersEnd { this.isVisible(testApp) }
+ flicker.assertLayersEnd { this.isVisible(testApp) }
}
/** {@inheritDoc} */
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
index b865999..9b4e39c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleScreen.kt
@@ -19,9 +19,9 @@
import android.platform.test.annotations.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -40,7 +40,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class LaunchBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class LaunchBubbleScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -59,6 +59,6 @@
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
index bf4d7d4..b3a2ad3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt
@@ -22,10 +22,10 @@
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.Test
@@ -45,7 +45,7 @@
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-open class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(testSpec) {
+open class MultiBubblesScreen(flicker: FlickerTest) : BaseBubbleScreen(flicker) {
@Before
open fun before() {
@@ -87,6 +87,6 @@
@Presubmit
@Test
open fun testAppIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
index 57adeab..191f4fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreenShellTransit.kt
@@ -18,9 +18,9 @@
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.runner.RunWith
@@ -30,8 +30,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FlakyTest(bugId = 217777115)
-class MultiBubblesScreenShellTransit(testSpec: FlickerTestParameter) :
- MultiBubblesScreen(testSpec) {
+class MultiBubblesScreenShellTransit(flicker: FlickerTest) : MultiBubblesScreen(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 7546a55..5e898e8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -18,15 +18,15 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -59,7 +59,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 238367575)
-class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipTest(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -73,7 +73,7 @@
// close gracefully so that onActivityUnpinned() can be called before force exit
pipApp.closePipWindow(wmHelper)
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
}
@@ -83,7 +83,7 @@
@FlakyTest(bugId = 256863309)
@Test
override fun pipLayerReduces() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -96,8 +96,8 @@
@Test
fun pipLayerMovesTowardsRightBottomCorner() {
// in gestural nav the swipe makes PiP first go upwards
- Assume.assumeFalse(testSpec.isGesturalNavigation)
- testSpec.assertLayers {
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
// Pip animates towards the right bottom corner, but because it is being resized at the
// same time, it is possible it shrinks first quickly below the default position and get
@@ -112,7 +112,7 @@
@Test
override fun focusChanges() {
// in gestural nav the focus goes to different activity on swipe up
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index c8aa6d2..79feeaa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -17,14 +17,14 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -56,7 +56,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTest(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -68,7 +68,7 @@
pipApp.enableEnterPipOnUserLeaveHint()
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
}
@@ -78,10 +78,10 @@
@Presubmit
@Test
override fun pipAppLayerAlwaysVisible() {
- if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
+ if (!flicker.scenario.isGesturalNavigation) super.pipAppLayerAlwaysVisible()
else {
// pip layer in gesture nav will disappear during transition
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(pipApp).then().isInvisible(pipApp).then().isVisible(pipApp)
}
}
@@ -91,7 +91,7 @@
@Test
override fun pipLayerReduces() {
// in gestural nav the pip enters through alpha animation
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.pipLayerReduces()
}
@@ -99,7 +99,7 @@
@Test
override fun focusChanges() {
// in gestural nav the focus goes to different activity on swipe up
- Assume.assumeFalse(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
super.focusChanges()
}
@@ -112,11 +112,11 @@
@Presubmit
@Test
override fun pipLayerRemainInsideVisibleBounds() {
- if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
+ if (!flicker.scenario.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds()
else {
// pip layer in gesture nav will disappear during transition
- testSpec.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
- testSpec.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersStart { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(pipApp).coversAtMost(displayBounds) }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 2b629e7..1a76142 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -17,16 +17,16 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,7 +57,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class EnterPipTest(flicker: FlickerTest) : PipTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
@@ -68,7 +68,7 @@
pipApp.launchViaIntent(wmHelper)
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
}
@@ -79,16 +79,14 @@
@Presubmit
@Test
open fun pipAppWindowAlwaysVisible() {
- testSpec.assertWm { this.isAppWindowVisible(pipApp) }
+ flicker.assertWm { this.isAppWindowVisible(pipApp) }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
open fun pipAppLayerAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(pipApp) }
+ flicker.assertLayers { this.isVisible(pipApp) }
}
/**
@@ -98,7 +96,7 @@
@Presubmit
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -108,14 +106,14 @@
@Presubmit
@Test
open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/** Checks that the visible region of [pipApp] always reduces during the animation */
@Presubmit
@Test
open fun pipLayerReduces() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
@@ -127,7 +125,7 @@
@Presubmit
@Test
fun pipWindowBecomesPinned() {
- testSpec.assertWm {
+ flicker.assertWm {
invoke("pipWindowIsNotPinned") { it.isNotPinned(pipApp) }
.then()
.invoke("pipWindowIsPinned") { it.isPinned(pipApp) }
@@ -138,7 +136,7 @@
@Presubmit
@Test
fun launcherLayerBecomesVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isInvisible(ComponentNameMatcher.LAUNCHER)
.then()
.isVisible(ComponentNameMatcher.LAUNCHER)
@@ -152,21 +150,22 @@
@Presubmit
@Test
open fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges(pipApp.`package`, "NexusLauncherActivity") }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index b4594de..2b90243 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -19,22 +19,22 @@
import android.app.Activity
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
import org.junit.Assume
@@ -51,32 +51,31 @@
* To run this test: `atest WMShellFlickerTests:EnterPipToOtherOrientationTest`
*
* Actions:
+ * ```
* Launch [testApp] on a fixed portrait orientation
* Launch [pipApp] on a fixed landscape orientation
* Broadcast action [ACTION_ENTER_PIP] to enter pip mode
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterPipToOtherOrientationTest(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
+class EnterPipToOtherOrientationTest(flicker: FlickerTest) : PipTransition(flicker) {
private val testApp = FixedOrientationAppHelper(instrumentation)
- private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
- private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
+ private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90)
+ private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0)
- /**
- * Defines the transition used to run the test
- */
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
@@ -85,19 +84,18 @@
// Launch a portrait only app on the fullscreen stack
testApp.launchViaIntent(
- wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString()
- )
+ wmHelper,
+ stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
)
// Launch the PiP activity fixed as landscape
pipApp.launchViaIntent(
- wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()
- )
+ wmHelper,
+ stringExtras =
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
)
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
RemoveAllTasksButHomeRule.removeAllTasksButHome()
pipApp.exit(wmHelper)
testApp.exit(wmHelper)
@@ -107,7 +105,8 @@
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
// during rotation the status bar becomes invisible and reappears at the end
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withPipShown()
.withNavOrTaskBarVisible()
.withStatusBarVisible()
@@ -116,21 +115,21 @@
}
/**
- * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation]
- * to fix a orientation, Tablets instead keep the same orientation and add letterboxes
+ * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+ * fix a orientation, Tablets instead keep the same orientation and add letterboxes
*/
@Before
fun setup() {
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeFalse(flicker.scenario.isTablet)
}
/**
- * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at
- * the start and end of the transition
+ * Checks that the [ComponentNameMatcher.NAV_BAR] has the correct position at the start and end
+ * of the transition
*/
@FlakyTest
@Test
- override fun navBarLayerPositionAtStartAndEnd() = testSpec.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = flicker.navBarLayerPositionAtStartAndEnd()
/**
* Checks that all parts of the screen are covered at the start and end of the transition
@@ -139,7 +138,7 @@
*/
@Presubmit
@Test
- fun entireScreenCoveredAtStartAndEnd() = testSpec.entireScreenCovered(allStates = false)
+ fun entireScreenCoveredAtStartAndEnd() = flicker.entireScreenCovered(allStates = false)
@FlakyTest(bugId = 251219769)
@Test
@@ -147,89 +146,65 @@
super.entireScreenCovered()
}
- /**
- * Checks [pipApp] window remains visible and on top throughout the transition
- */
+ /** Checks [pipApp] window remains visible and on top throughout the transition */
@Presubmit
@Test
fun pipAppWindowIsAlwaysOnTop() {
- testSpec.assertWm {
- isAppWindowOnTop(pipApp)
- }
+ flicker.assertWm { isAppWindowOnTop(pipApp) }
}
- /**
- * Checks that [testApp] window is not visible at the start
- */
+ /** Checks that [testApp] window is not visible at the start */
@Presubmit
@Test
fun testAppWindowInvisibleOnStart() {
- testSpec.assertWmStart {
- isAppWindowInvisible(testApp)
- }
+ flicker.assertWmStart { isAppWindowInvisible(testApp) }
}
- /**
- * Checks that [testApp] window is visible at the end
- */
+ /** Checks that [testApp] window is visible at the end */
@Presubmit
@Test
fun testAppWindowVisibleOnEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(testApp)
- }
+ flicker.assertWmEnd { isAppWindowVisible(testApp) }
}
- /**
- * Checks that [testApp] layer is not visible at the start
- */
+ /** Checks that [testApp] layer is not visible at the start */
@Presubmit
@Test
fun testAppLayerInvisibleOnStart() {
- testSpec.assertLayersStart {
- isInvisible(testApp)
- }
+ flicker.assertLayersStart { isInvisible(testApp) }
}
- /**
- * Checks that [testApp] layer is visible at the end
- */
+ /** Checks that [testApp] layer is visible at the end */
@Presubmit
@Test
fun testAppLayerVisibleOnEnd() {
- testSpec.assertLayersEnd {
- isVisible(testApp)
- }
+ flicker.assertLayersEnd { isVisible(testApp) }
}
/**
- * Checks that the visible region of [pipApp] covers the full display area at the start of
- * the transition
+ * Checks that the visible region of [pipApp] covers the full display area at the start of the
+ * transition
*/
@Presubmit
@Test
fun pipAppLayerCoversFullScreenOnStart() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp).coversExactly(startingBounds)
- }
+ flicker.assertLayersStart { visibleRegion(pipApp).coversExactly(startingBounds) }
}
/**
- * Checks that the visible region of [testApp] plus the visible region of [pipApp]
- * cover the full display area at the end of the transition
+ * Checks that the visible region of [testApp] plus the visible region of [pipApp] cover the
+ * full display area at the end of the transition
*/
@Presubmit
@Test
fun testAppPlusPipLayerCoversFullScreenOnEnd() {
- testSpec.assertLayersEnd {
+ flicker.assertLayersEnd {
val pipRegion = visibleRegion(pipApp).region
- visibleRegion(testApp)
- .plus(pipRegion)
- .coversExactly(endingBounds)
+ visibleRegion(testApp).plus(pipRegion).coversExactly(endingBounds)
}
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
@@ -239,16 +214,15 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
index 1dc03b9..7466916 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import org.junit.Test
/** Base class for pip expand tests */
-abstract class ExitPipToAppTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class ExitPipToAppTransition(flicker: FlickerTest) : PipTransition(flicker) {
protected val testApp = SimpleAppHelper(instrumentation)
/**
@@ -32,7 +32,7 @@
@Presubmit
@Test
open fun pipAppWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -42,7 +42,7 @@
@Presubmit
@Test
open fun pipAppLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -52,7 +52,7 @@
@Presubmit
@Test
open fun showBothAppWindowsThenHidePip() {
- testSpec.assertWm {
+ flicker.assertWm {
// when the activity is STOPPING, sometimes it becomes invisible in an entry before
// the window, sometimes in the same entry. This occurs because we log 1x per frame
// thus we ignore activity here
@@ -71,7 +71,7 @@
@Presubmit
@Test
open fun showBothAppLayersThenHidePip() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isVisible(testApp).isVisible(pipApp).then().isInvisible(testApp).isVisible(pipApp)
}
}
@@ -83,7 +83,7 @@
@Presubmit
@Test
open fun testPlusPipAppsCoverFullScreenAtStart() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
val pipRegion = visibleRegion(pipApp).region
visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
}
@@ -96,14 +96,14 @@
@Presubmit
@Test
open fun pipAppCoversFullScreenAtEnd() {
- testSpec.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(displayBounds) }
}
/** Checks that the visible region of [pipApp] always expands during the animation */
@Presubmit
@Test
open fun pipLayerExpands() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 3b8bb90..1b5c227 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -17,20 +17,20 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.LAUNCHER
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Test
/** Base class for exiting pip (closing pip window) without returning to the app */
-abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+abstract class ExitPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
- setup { this.setRotation(testSpec.startRotation) }
- teardown { this.setRotation(Surface.ROTATION_0) }
+ setup { this.setRotation(flicker.scenario.startRotation) }
+ teardown { this.setRotation(PlatformConsts.Rotation.ROTATION_0) }
}
/**
@@ -45,16 +45,16 @@
// When Shell transition is enabled, we change the windowing mode at start, but
// update the visibility after the transition is finished, so we can't check isNotPinned
// and isAppWindowInvisible in the same assertion block.
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("hasPipWindow") {
it.isPinned(pipApp).isAppWindowVisible(pipApp).isAppWindowOnTop(pipApp)
}
.then()
.invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowNotOnTop(pipApp) }
}
- testSpec.assertWmEnd { isAppWindowInvisible(pipApp) }
+ flicker.assertWmEnd { isAppWindowInvisible(pipApp) }
} else {
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("hasPipWindow") { it.isPinned(pipApp).isAppWindowVisible(pipApp) }
.then()
.invoke("!hasPipWindow") { it.isNotPinned(pipApp).isAppWindowInvisible(pipApp) }
@@ -69,7 +69,7 @@
@Presubmit
@Test
open fun pipLayerBecomesInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(pipApp)
.isVisible(LAUNCHER)
.then()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 6bf7e8c..1420f8ce 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -18,12 +18,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,30 +36,29 @@
* To run this test: `atest WMShellFlickerTests:ExitPipViaExpandButtonClickTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Launch another full screen mode [testApp]
* Expand [pipApp] app to full screen by clicking on the pip window and
* then on the expand button
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaExpandButtonClickTest(
- testSpec: FlickerTestParameter
-) : ExitPipToAppTransition(testSpec) {
+class ExitPipViaExpandButtonClickTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
- /**
- * Defines the transition used to run the test
- */
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
setup {
@@ -70,34 +69,29 @@
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.StateSyncBuilder()
- .withWindowSurfaceDisappeared(testApp)
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
}
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
- /** {@inheritDoc} */
- @FlakyTest(bugId = 197726610)
- @Test
- override fun pipLayerExpands() = super.pipLayerExpands()
+ /** {@inheritDoc} */
+ @FlakyTest(bugId = 197726610) @Test override fun pipLayerExpands() = super.pipLayerExpands()
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 3356d3e..dffbe7e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -18,13 +18,13 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,7 +57,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransition(testSpec) {
+class ExitPipViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
@@ -74,10 +74,8 @@
}
}
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ /** {@inheritDoc} */
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -113,14 +111,15 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index d195abb..232c025 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,7 +54,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+class ExitPipWithDismissButtonTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -69,21 +69,22 @@
@Presubmit
@Test
fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
+ flicker.assertEventLog { this.focusChanges("PipMenuView", "NexusLauncherActivity") }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index f7a2447..dbbfdcc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -54,7 +54,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransition(testSpec) {
+class ExitPipWithSwipeDownTest(flicker: FlickerTest) : ExitPipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -64,7 +64,7 @@
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
val barComponent =
- if (testSpec.isTablet) {
+ if (flicker.scenario.isTablet) {
ComponentNameMatcher.TASK_BAR
} else {
ComponentNameMatcher.NAV_BAR
@@ -92,21 +92,22 @@
@Presubmit
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog { this.focusDoesNotChange() }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index fa5ce5b..f213cc9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -17,13 +17,13 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.FlakyTest
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -36,28 +36,27 @@
* To run this test: `atest WMShellFlickerTests:ExpandPipOnDoubleClickTest`
*
* Actions:
+ * ```
* Launch an app in pip mode [pipApp],
* Expand [pipApp] app to its maximum pip size by double clicking on it
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- transitions {
- pipApp.doubleClickPipWindow(wmHelper)
- }
- }
+ get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
@@ -66,9 +65,7 @@
@FlakyTest(bugId = 249308003)
@Test
fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) {
- coversAtMost(displayBounds)
- }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -78,40 +75,28 @@
@FlakyTest(bugId = 249308003)
@Test
fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) {
- coversAtMost(displayBounds)
- }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
- /**
- * Checks [pipApp] window remains visible throughout the animation
- */
+ /** Checks [pipApp] window remains visible throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun pipWindowIsAlwaysVisible() {
- testSpec.assertWm {
- isAppWindowVisible(pipApp)
- }
+ flicker.assertWm { isAppWindowVisible(pipApp) }
}
- /**
- * Checks [pipApp] layer remains visible throughout the animation
- */
+ /** Checks [pipApp] layer remains visible throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun pipLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(pipApp)
- }
+ flicker.assertLayers { isVisible(pipApp) }
}
- /**
- * Checks that the visible region of [pipApp] always expands during the animation
- */
+ /** Checks that the visible region of [pipApp] always expands during the animation */
@FlakyTest(bugId = 249308003)
@Test
fun pipLayerExpands() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.coversAtLeast(previous.visibleRegion.region)
@@ -122,7 +107,7 @@
@FlakyTest(bugId = 249308003)
@Test
fun pipSameAspectRatio() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
current.visibleRegion.isSameAspectRatio(previous.visibleRegion)
@@ -130,37 +115,25 @@
}
}
- /**
- * Checks [pipApp] window remains pinned throughout the animation
- */
+ /** Checks [pipApp] window remains pinned throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun windowIsAlwaysPinned() {
- testSpec.assertWm {
- this.invoke("hasPipWindow") { it.isPinned(pipApp) }
- }
+ flicker.assertWm { this.invoke("hasPipWindow") { it.isPinned(pipApp) } }
}
- /**
- * Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation
- */
+ /** Checks [ComponentMatcher.LAUNCHER] layer remains visible throughout the animation */
@FlakyTest(bugId = 249308003)
@Test
fun launcherIsAlwaysVisible() {
- testSpec.assertLayers {
- isVisible(ComponentNameMatcher.LAUNCHER)
- }
+ flicker.assertLayers { isVisible(ComponentNameMatcher.LAUNCHER) }
}
- /**
- * Checks that the focus doesn't change between windows during the transition
- */
+ /** Checks that the focus doesn't change between windows during the transition */
@FlakyTest(bugId = 216306753)
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog {
- this.focusDoesNotChange()
- }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
@FlakyTest(bugId = 216306753)
@@ -233,16 +206,15 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index bcd01a4..34f6659 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -17,40 +17,32 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Postsubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-/**
- * Test expanding a pip window via pinch out gesture.
- */
+/** Test expanding a pip window via pinch out gesture. */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ExpandPipOnPinchOpenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- transitions {
- pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30)
- }
- }
+ get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30) } }
- /**
- * Checks that the visible region area of [pipApp] always increases during the animation.
- */
+ /** Checks that the visible region area of [pipApp] always increases during the animation. */
@Postsubmit
@Test
fun pipLayerAreaIncreases() {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
pipLayerList.zipWithNext { previous, current ->
previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
@@ -62,16 +54,15 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 0c0228e..e9847fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -18,11 +18,11 @@
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.Direction
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,71 +36,56 @@
* To run this test: `atest WMShellFlickerTests:MovePipUpShelfHeightChangeTest`
*
* Actions:
+ * ```
* Launch [pipApp] in pip mode
* Press home
* Launch [testApp]
* Check if pip window moves down (visually)
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class MovePipDownShelfHeightChangeTest(
- testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
-// @Before
-// fun before() {
-// Assume.assumeFalse(isShellTransitionsEnabled)
-// }
-
- /**
- * Defines the transition used to run the test
- */
+class MovePipDownShelfHeightChangeTest(flicker: FlickerTest) :
+ MovePipShelfHeightTransition(flicker) {
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = buildTransition {
teardown {
tapl.pressHome()
testApp.exit(wmHelper)
}
- transitions {
- testApp.launchViaIntent(wmHelper)
- }
+ transitions { testApp.launchViaIntent(wmHelper) }
}
- /**
- * Checks that the visible region of [pipApp] window always moves down during the animation.
- */
- @Presubmit
- @Test
- fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+ /** Checks that the visible region of [pipApp] window always moves down during the animation. */
+ @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
- /**
- * Checks that the visible region of [pipApp] layer always moves down during the animation.
- */
- @Presubmit
- @Test
- fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
+ /** Checks that the visible region of [pipApp] layer always moves down during the animation. */
+ @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
index b401067..35525cb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
@@ -17,29 +17,28 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.wm.shell.flicker.Direction
import org.junit.Test
/** Base class for pip tests with Launcher shelf height change */
-abstract class MovePipShelfHeightTransition(testSpec: FlickerTestParameter) :
- PipTransition(testSpec) {
+abstract class MovePipShelfHeightTransition(flicker: FlickerTest) : PipTransition(flicker) {
protected val testApp = FixedOrientationAppHelper(instrumentation)
/** Checks [pipApp] window remains visible throughout the animation */
@Presubmit
@Test
open fun pipWindowIsAlwaysVisible() {
- testSpec.assertWm { isAppWindowVisible(pipApp) }
+ flicker.assertWm { isAppWindowVisible(pipApp) }
}
/** Checks [pipApp] layer remains visible throughout the animation */
@Presubmit
@Test
open fun pipLayerIsAlwaysVisible() {
- testSpec.assertLayers { isVisible(pipApp) }
+ flicker.assertLayers { isVisible(pipApp) }
}
/**
@@ -49,7 +48,7 @@
@Presubmit
@Test
open fun pipWindowRemainInsideVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -59,7 +58,7 @@
@Presubmit
@Test
open fun pipLayerRemainInsideVisibleBounds() {
- testSpec.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ flicker.assertLayersVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
/**
@@ -67,7 +66,7 @@
* during the animation.
*/
protected fun pipWindowMoves(direction: Direction) {
- testSpec.assertWm {
+ flicker.assertWm {
val pipWindowFrameList =
this.windowStates { pipApp.windowMatchesAnyOf(it) && it.isVisible }.map { it.frame }
when (direction) {
@@ -83,7 +82,7 @@
* during the animation.
*/
protected fun pipLayerMoves(direction: Direction) {
- testSpec.assertLayers {
+ flicker.assertLayers {
val pipLayerRegionList =
this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
.map { it.visibleRegion }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
index 7f8ef32..3a12a34 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpShelfHeightChangeTest.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.Direction
import org.junit.FixMethodOrder
import org.junit.Test
@@ -36,68 +36,55 @@
* To run this test: `atest WMShellFlickerTests:MovePipDownShelfHeightChangeTest`
*
* Actions:
+ * ```
* Launch [pipApp] in pip mode
* Launch [testApp]
* Press home
* Check if pip window moves up (visually)
- *
+ * ```
* Notes:
+ * ```
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
* are inherited [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
* apps are running before setup
+ * ```
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class MovePipUpShelfHeightChangeTest(
- testSpec: FlickerTestParameter
-) : MovePipShelfHeightTransition(testSpec) {
- /**
- * Defines the transition used to run the test
- */
+open class MovePipUpShelfHeightChangeTest(flicker: FlickerTest) :
+ MovePipShelfHeightTransition(flicker) {
+ /** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition() {
- setup {
- testApp.launchViaIntent(wmHelper)
+ get() =
+ buildTransition() {
+ setup { testApp.launchViaIntent(wmHelper) }
+ transitions { tapl.pressHome() }
+ teardown { testApp.exit(wmHelper) }
}
- transitions {
- tapl.pressHome()
- }
- teardown {
- testApp.exit(wmHelper)
- }
- }
- /**
- * Checks that the visible region of [pipApp] window always moves up during the animation.
- */
- @Presubmit
- @Test
- fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
+ /** Checks that the visible region of [pipApp] window always moves up during the animation. */
+ @Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
- /**
- * Checks that the visible region of [pipApp] layer always moves up during the animation.
- */
- @Presubmit
- @Test
- fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
+ /** Checks that the visible region of [pipApp] layer always moves up during the animation. */
+ @Presubmit @Test fun pipLayerMovesUp() = pipLayerMoves(Direction.UP)
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
- * repetitions, screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 3b64d21..12d6362 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -17,17 +17,17 @@
package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.FixMethodOrder
@@ -41,7 +41,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class PipKeyboardTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipKeyboardTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
@Before
@@ -54,11 +54,11 @@
get() = buildTransition {
setup {
imeApp.launchViaIntent(wmHelper)
- setRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
}
teardown {
imeApp.exit(wmHelper)
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
}
transitions {
// open the soft keyboard
@@ -74,8 +74,8 @@
@Presubmit
@Test
open fun pipInVisibleBounds() {
- testSpec.assertWmVisibleRegion(pipApp) {
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ flicker.assertWmVisibleRegion(pipApp) {
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
coversAtMost(displayBounds)
}
}
@@ -84,7 +84,7 @@
@Presubmit
@Test
open fun pipIsAboveAppWindow() {
- testSpec.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
+ flicker.assertWmTag(TAG_IME_VISIBLE) { isAboveWindow(ComponentNameMatcher.IME, pipApp) }
}
companion object {
@@ -92,9 +92,10 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
index 2a82c00..901814e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -18,9 +18,9 @@
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
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -33,7 +33,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
+class PipKeyboardTestShellTransit(flicker: FlickerTest) : PipKeyboardTest(flicker) {
@Before
override fun before() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 7de5494..eee00bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -18,16 +18,15 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -45,7 +44,7 @@
* ```
* Launch a [pipApp] in pip mode
* Launch another app [fixedApp] (appears below pip)
- * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
+ * Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
* (usually, 0->90 and 90->0)
* ```
* Notes:
@@ -62,10 +61,10 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class PipRotationTest(flicker: FlickerTest) : PipTransition(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
- private val screenBoundsStart = WindowUtils.getDisplayBounds(testSpec.startRotation)
- private val screenBoundsEnd = WindowUtils.getDisplayBounds(testSpec.endRotation)
+ private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
+ private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
@Before
open fun before() {
@@ -76,9 +75,9 @@
get() = buildTransition {
setup {
testApp.launchViaIntent(wmHelper)
- setRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
}
- transitions { setRotation(testSpec.endRotation) }
+ transitions { setRotation(flicker.scenario.endRotation) }
}
/** Checks the position of the navigation bar at the start and end of the transition */
@@ -90,14 +89,14 @@
@Presubmit
@Test
fun fixedAppLayer_StartingBounds() {
- testSpec.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
+ flicker.assertLayersStart { visibleRegion(testApp).coversAtMost(screenBoundsStart) }
}
/** Checks that [testApp] layer is within [screenBoundsEnd] at the end of the transition */
@Presubmit
@Test
fun fixedAppLayer_EndingBounds() {
- testSpec.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
+ flicker.assertLayersEnd { visibleRegion(testApp).coversAtMost(screenBoundsEnd) }
}
/**
@@ -107,7 +106,7 @@
@Presubmit
@Test
fun appLayers_StartingBounds() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsStart)
}
}
@@ -119,14 +118,12 @@
@Presubmit
@Test
fun appLayers_EndingBounds() {
- testSpec.assertLayersEnd {
- visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd)
- }
+ flicker.assertLayersEnd { visibleRegion(testApp.or(pipApp)).coversExactly(screenBoundsEnd) }
}
/** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
private fun pipLayerRotates_StartingBounds_internal() {
- testSpec.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
+ flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(screenBoundsStart) }
}
/** Checks that [pipApp] layer is within [screenBoundsStart] at the start of the transition */
@@ -140,7 +137,7 @@
@Presubmit
@Test
fun pipLayerRotates_EndingBounds() {
- testSpec.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversAtMost(screenBoundsEnd) }
}
/**
@@ -149,7 +146,7 @@
@Presubmit
@Test
fun pipIsAboveFixedAppWindow_Start() {
- testSpec.assertWmStart { isAboveWindow(pipApp, testApp) }
+ flicker.assertWmStart { isAboveWindow(pipApp, testApp) }
}
/**
@@ -158,7 +155,7 @@
@Presubmit
@Test
fun pipIsAboveFixedAppWindow_End() {
- testSpec.assertWmEnd { isAboveWindow(pipApp, testApp) }
+ flicker.assertWmEnd { isAboveWindow(pipApp, testApp) }
}
@Presubmit
@@ -171,16 +168,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring repetitions, screen orientation
+ * and navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
index 983cb1c..d0d9167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest_ShellTransit.kt
@@ -18,9 +18,9 @@
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -38,7 +38,7 @@
* ```
* Launch a [pipApp] in pip mode
* Launch another app [fixedApp] (appears below pip)
- * Rotate the screen from [testSpec.startRotation] to [testSpec.endRotation]
+ * Rotate the screen from [flicker.scenario.startRotation] to [flicker.scenario.endRotation]
* (usually, 0->90 and 90->0)
* ```
* Notes:
@@ -56,7 +56,7 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 239575053)
-class PipRotationTest_ShellTransit(testSpec: FlickerTestParameter) : PipRotationTest(testSpec) {
+class PipRotationTest_ShellTransit(flicker: FlickerTest) : PipRotationTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index dfa2510..0e0be79 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -18,19 +18,19 @@
import android.app.Instrumentation
import android.content.Intent
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.PipAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.BaseTest
-abstract class PipTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected val pipApp = PipAppHelper(instrumentation)
- protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ protected val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
// Helper class to process test actions by broadcast.
@@ -67,12 +67,12 @@
): FlickerBuilder.() -> Unit {
return {
setup {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
removeAllTasksButHome()
pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
}
teardown {
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
removeAllTasksButHome()
pipApp.exit(wmHelper)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index f0093e6..157aa98 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -20,19 +20,19 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import org.junit.Assume
import org.junit.Before
@@ -43,20 +43,18 @@
import org.junit.runners.Parameterized
/**
- * Test exiting Pip with orientation changes.
- * To run this test: `atest WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
+ * Test exiting Pip with orientation changes. To run this test: `atest
+ * WMShellFlickerTests:SetRequestedOrientationWhilePinnedTest`
*/
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SetRequestedOrientationWhilePinnedTest(
- testSpec: FlickerTestParameter
-) : PipTransition(testSpec) {
- private val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0)
- private val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90)
+open class SetRequestedOrientationWhilePinnedTest(flicker: FlickerTest) : PipTransition(flicker) {
+ private val startingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_0)
+ private val endingBounds = WindowUtils.getDisplayBounds(PlatformConsts.Rotation.ROTATION_90)
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
setup {
@@ -64,30 +62,35 @@
device.wakeUpAndGoToHomeScreen()
// Launch the PiP activity fixed as landscape.
- pipApp.launchViaIntent(wmHelper, stringExtras = mapOf(
- EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
+ pipApp.launchViaIntent(
+ wmHelper,
+ stringExtras =
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
// Enter PiP.
broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
// System bar may fade out during fixed rotation.
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withPipShown()
- .withRotation(Surface.ROTATION_0)
+ .withRotation(PlatformConsts.Rotation.ROTATION_0)
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
}
teardown {
pipApp.exit(wmHelper)
- setRotation(Surface.ROTATION_0)
+ setRotation(PlatformConsts.Rotation.ROTATION_0)
removeAllTasksButHome()
}
transitions {
// Launch the activity back into fullscreen and ensure that it is now in landscape
pipApp.launchViaIntent(wmHelper)
// System bar may fade out during fixed rotation.
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withFullScreenApp(pipApp)
- .withRotation(Surface.ROTATION_90)
+ .withRotation(PlatformConsts.Rotation.ROTATION_90)
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
@@ -95,34 +98,32 @@
}
/**
- * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation]
- * to fix a orientation, Tablets instead keep the same orientation and add letterboxes
+ * This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
+ * fix a orientation, Tablets instead keep the same orientation and add letterboxes
*/
@Before
fun setup() {
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeFalse(flicker.scenario.isTablet)
}
@Presubmit
@Test
fun displayEndsAt90Degrees() {
- testSpec.assertWmEnd {
- hasRotation(Surface.ROTATION_90)
- }
+ flicker.assertWmEnd { hasRotation(PlatformConsts.Rotation.ROTATION_90) }
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Presubmit
@Test
override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Presubmit
@Test
override fun statusBarLayerIsVisibleAtStartAndEnd() =
super.statusBarLayerIsVisibleAtStartAndEnd()
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@FlakyTest
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
@@ -130,23 +131,17 @@
@Presubmit
@Test
fun pipWindowInsideDisplay() {
- testSpec.assertWmStart {
- visibleRegion(pipApp).coversAtMost(startingBounds)
- }
+ flicker.assertWmStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
}
@Presubmit
@Test
fun pipAppShowsOnTop() {
- testSpec.assertWmEnd {
- isAppWindowOnTop(pipApp)
- }
+ flicker.assertWmEnd { isAppWindowOnTop(pipApp) }
}
private fun pipLayerInsideDisplay_internal() {
- testSpec.assertLayersStart {
- visibleRegion(pipApp).coversAtMost(startingBounds)
- }
+ flicker.assertLayersStart { visibleRegion(pipApp).coversAtMost(startingBounds) }
}
@Presubmit
@@ -166,40 +161,35 @@
@Presubmit
@Test
fun pipAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(pipApp)
- }
+ flicker.assertWm { this.isAppWindowVisible(pipApp) }
}
@Presubmit
@Test
fun pipAppLayerCoversFullScreen() {
- testSpec.assertLayersEnd {
- visibleRegion(pipApp).coversExactly(endingBounds)
- }
+ flicker.assertLayersEnd { visibleRegion(pipApp).coversExactly(endingBounds) }
}
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Postsubmit
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- /** {@inheritDoc} */
+ /** {@inheritDoc} */
@Postsubmit
@Test
override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
index 2cb18f9..a16f5f6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/PipTestBase.kt
@@ -24,7 +24,10 @@
import org.junit.Before
import org.junit.runners.Parameterized
-abstract class PipTestBase(protected val rotationName: String, protected val rotation: Int) {
+abstract class PipTestBase(
+ protected val rotationName: String,
+ protected val rotation: Int
+) {
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
val packageManager: PackageManager = instrumentation.context.packageManager
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 9533b91..65cbea0 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
@@ -20,10 +20,10 @@
import android.platform.test.annotations.IwTest
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
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
@@ -49,7 +49,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class CopyContentInSplit(flicker: FlickerTest) : SplitScreenBase(flicker) {
private val textEditApp = SplitScreenUtils.getIme(instrumentation)
private val MagnifierLayer = ComponentNameMatcher("", "magnifier surface bbq wrapper#")
private val PopupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
@@ -72,29 +72,29 @@
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(primaryApp)
- testSpec.appWindowIsVisibleAtStart(textEditApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(textEditApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(textEditApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(textEditApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
// The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
}
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit @Test fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
- @Presubmit @Test fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
+ @Presubmit @Test fun textEditAppLayerKeepVisible() = flicker.layerKeepVisible(textEditApp)
@Presubmit
@Test
fun primaryAppBoundsKeepVisible() =
- testSpec.splitAppLayerBoundsKeepVisible(
+ flicker.splitAppLayerBoundsKeepVisible(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -103,21 +103,18 @@
@Presubmit
@Test
fun textEditAppBoundsKeepVisible() =
- testSpec.splitAppLayerBoundsKeepVisible(
+ flicker.splitAppLayerBoundsKeepVisible(
textEditApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
)
- @Presubmit @Test fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
- @Presubmit @Test fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
+ @Presubmit @Test fun textEditAppWindowKeepVisible() = flicker.appWindowKeepVisible(textEditApp)
/** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -164,15 +161,18 @@
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
- ignoreLayers = listOf(
- ComponentNameMatcher.SPLASH_SCREEN,
- ComponentNameMatcher.SNAPSHOT,
- ComponentNameMatcher.IME_SNAPSHOT,
- EdgeExtensionComponentMatcher(),
- MagnifierLayer,
- PopupWindowLayer))
+ ignoreLayers =
+ listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ ComponentNameMatcher.IME_SNAPSHOT,
+ EdgeExtensionComponentMatcher(),
+ MagnifierLayer,
+ PopupWindowLayer
+ )
+ )
}
}
@@ -185,9 +185,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 4757498..d0f02e2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -21,11 +21,11 @@
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -49,54 +49,62 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class DismissSplitScreenByDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
transitions {
if (tapl.isTablet) {
- SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
- dragToRight = false, dragToBottom = true)
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = false,
+ dragToBottom = true
+ )
} else {
- SplitScreenUtils.dragDividerToDismissSplit(device, wmHelper,
- dragToRight = true, dragToBottom = true)
+ SplitScreenUtils.dragDividerToDismissSplit(
+ device,
+ wmHelper,
+ dragToRight = true,
+ dragToBottom = true
+ )
}
- wmHelper.StateSyncBuilder()
- .withFullScreenApp(secondaryApp)
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify()
}
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
@Presubmit
@Test
- fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+ fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@Presubmit
@Test
- fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+ fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+ fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+ fun primaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@Presubmit
@Test
fun secondaryAppBoundsIsFullscreenAtEnd() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(secondaryApp)
.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
@@ -109,7 +117,7 @@
.contains(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
.invoke("secondaryAppBoundsIsFullscreenAtEnd") {
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.endRotation)
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
it.visibleRegion(secondaryApp).coversExactly(displayBounds)
}
}
@@ -117,35 +125,29 @@
@Presubmit
@Test
- fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+ fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -156,26 +158,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -192,8 +190,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 1d61955..b44b681 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -20,10 +20,10 @@
import android.platform.test.annotations.IwTest
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
@@ -44,89 +44,81 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DismissSplitScreenByGoHome(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class DismissSplitScreenByGoHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
transitions {
tapl.goHome()
- wmHelper.StateSyncBuilder()
- .withHomeActivityVisible()
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
}
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+ fun cujCompleted() = flicker.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
+ fun splitScreenDividerBecomesInvisible() = flicker.splitScreenDividerBecomesInvisible()
@FlakyTest(bugId = 241525302)
@Test
- fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+ fun primaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
@Test
- fun secondaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
+ fun secondaryAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(primaryApp)
// TODO(b/245472831): Move back to presubmit after shell transitions landing.
@FlakyTest(bugId = 245472831)
@Test
- fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- primaryApp,
- landscapePosLeft = tapl.isTablet,
- portraitPosTop = false
- )
+ fun primaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@FlakyTest(bugId = 250530241)
@Test
- fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
- secondaryApp,
- landscapePosLeft = !tapl.isTablet,
- portraitPosTop = true
- )
+ fun secondaryAppBoundsBecomesInvisible() =
+ flicker.splitAppLayerBoundsBecomesInvisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
@Presubmit
@Test
- fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
+ fun primaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
+ fun secondaryAppWindowBecomesInvisible() = flicker.appWindowBecomesInvisible(secondaryApp)
/** {@inheritDoc} */
@FlakyTest(bugId = 251268711)
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@@ -137,26 +129,22 @@
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@FlakyTest
@@ -173,8 +161,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 8d771fe..5b656b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -21,10 +21,10 @@
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
@@ -50,35 +50,31 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class DragDividerToResize(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
- transitions {
- SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
@Before
fun before() {
- Assume.assumeTrue(tapl.isTablet || !testSpec.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(tapl.isTablet || !flicker.scenario.isLandscapeOrSeascapeAtStart)
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(primaryApp)
- testSpec.appWindowIsVisibleAtStart(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
// TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
// robust enough to get the correct end state.
@@ -86,16 +82,14 @@
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @Presubmit
- @Test
- fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
@Presubmit
@Test
fun secondaryAppLayerVisibilityChanges() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(secondaryApp)
.then()
.isInvisible(secondaryApp)
@@ -104,53 +98,47 @@
}
}
- @Presubmit
- @Test
- fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
+ @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
+ fun secondaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- primaryApp,
- landscapePosLeft = true,
- portraitPosTop = false
- )
+ fun primaryAppBoundsChanges() =
+ flicker.splitAppLayerBoundsChanges(
+ primaryApp,
+ landscapePosLeft = true,
+ portraitPosTop = false
+ )
@FlakyTest(bugId = 250530664)
@Test
- fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
- secondaryApp,
- landscapePosLeft = false,
- portraitPosTop = true
- )
+ fun secondaryAppBoundsChanges() =
+ flicker.splitAppLayerBoundsChanges(
+ secondaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = true
+ )
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -161,26 +149,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -197,8 +181,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 7378e21..4e36c36 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -19,13 +19,13 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -44,8 +44,8 @@
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from all apps.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from all apps. This test is only for large screen
+ * devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
*/
@@ -53,9 +53,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromAllApps(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
@@ -71,9 +69,9 @@
}
transitions {
tapl.launchedAppState.taskbar
- .openAllApps()
- .getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
@@ -81,13 +79,13 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitScreenDividerBecomesVisible()
+ flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -95,60 +93,54 @@
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersEnd {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- secondaryApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -159,26 +151,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -195,11 +183,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 0c03d31..5d37e85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -19,13 +19,13 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
@@ -43,8 +43,8 @@
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from notification.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from notification. This test is only for large
+ * screen devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
*/
@@ -52,9 +52,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromNotification(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreenBase(flicker) {
private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -78,21 +76,19 @@
SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp)
}
- teardown {
- sendNotificationApp.exit(wmHelper)
- }
+ teardown { sendNotificationApp.exit(wmHelper) }
}
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitScreenDividerBecomesVisible()
+ flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -100,20 +96,16 @@
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersEnd {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(sendNotificationApp)
.then()
.isVisible(sendNotificationApp)
@@ -129,50 +121,48 @@
@Test
fun secondaryAppLayerBecomesVisible_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.layerBecomesVisible(sendNotificationApp)
+ flicker.layerBecomesVisible(sendNotificationApp)
}
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- sendNotificationApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(sendNotificationApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(sendNotificationApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -183,26 +173,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -219,11 +205,10 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
)
}
}
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 dcadb5a..abf9426 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
@@ -17,14 +17,14 @@
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -41,8 +41,7 @@
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging a shortcut.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging a shortcut. This test is only for large screen devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
*/
@@ -50,13 +49,11 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromShortcut(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromShortcut(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
- Assume.assumeTrue(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isTablet)
}
override val transition: FlickerBuilder.() -> Unit
@@ -80,39 +77,46 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
- fromOtherApp = false, appExistAtStart = false)
+ fun cujCompleted() =
+ flicker.splitScreenEntered(
+ primaryApp,
+ secondaryApp,
+ fromOtherApp = false,
+ appExistAtStart = false
+ )
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- secondaryApp)
-
- @Presubmit
- @Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.notContains(secondaryApp)
.then()
.isAppWindowInvisible(secondaryApp, isOptional = true)
@@ -122,28 +126,22 @@
}
/** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -154,26 +152,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -190,11 +184,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 496d439..795a2c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -19,13 +19,13 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -44,8 +44,8 @@
import org.junit.runners.Parameterized
/**
- * Test enter split screen by dragging app icon from taskbar.
- * This test is only for large screen devices.
+ * Test enter split screen by dragging app icon from taskbar. This test is only for large screen
+ * devices.
*
* To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
*/
@@ -53,9 +53,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenByDragFromTaskbar(
- testSpec: FlickerTestParameter
-) : SplitScreenBase(testSpec) {
+class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(flicker) {
@Before
fun before() {
@@ -68,9 +66,7 @@
super.transition(this)
setup {
tapl.goHome()
- SplitScreenUtils.createShortcutOnHotseatIfNotExist(
- tapl, secondaryApp.appName
- )
+ SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
primaryApp.launchViaIntent(wmHelper)
}
transitions {
@@ -84,13 +80,13 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
@Presubmit
@Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitScreenDividerBecomesVisible()
+ flicker.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
@@ -98,20 +94,16 @@
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersEnd {
- this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- }
+ flicker.assertLayersEnd { this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) }
}
- @Presubmit
- @Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(secondaryApp)
.then()
.isVisible(secondaryApp)
@@ -127,50 +119,48 @@
@Test
fun secondaryAppLayerBecomesVisible_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.layerBecomesVisible(secondaryApp)
+ flicker.layerBecomesVisible(secondaryApp)
}
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = false, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = false,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
- secondaryApp)
+ fun secondaryAppBoundsBecomesVisible() =
+ flicker.splitAppLayerBoundsBecomesVisibleByDrag(secondaryApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -181,26 +171,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -217,10 +203,9 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index fb7b8b7..c09ca91 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -21,11 +21,11 @@
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
@@ -49,7 +49,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class EnterSplitScreenFromOverview(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -57,7 +57,8 @@
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
- wmHelper.StateSyncBuilder()
+ wmHelper
+ .StateSyncBuilder()
.withAppTransitionIdle()
.withHomeActivityVisible()
.waitForAndVerify()
@@ -71,72 +72,76 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
-
- @Presubmit
- @Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.splitAppLayerBoundsBecomesVisible(
- secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+ flicker.splitAppLayerBoundsBecomesVisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
}
@FlakyTest(bugId = 244407465)
@Test
fun secondaryAppBoundsBecomesVisible_shellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.splitAppLayerBoundsBecomesVisible(
- secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
+ flicker.splitAppLayerBoundsBecomesVisible(
+ secondaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
}
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
@FlakyTest(bugId = 251269324)
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@@ -147,26 +152,22 @@
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@FlakyTest(bugId = 252736515)
@@ -183,8 +184,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index c841333..8c0a303 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -17,12 +17,12 @@
package com.android.wm.shell.flicker.splitscreen
import android.content.Context
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.setRotation
import com.android.wm.shell.flicker.BaseTest
-abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class SplitScreenBase(flicker: FlickerTest) : BaseTest(flicker) {
protected val context: Context = instrumentation.context
protected val primaryApp = SplitScreenUtils.getPrimary(instrumentation)
protected val secondaryApp = SplitScreenUtils.getSecondary(instrumentation)
@@ -32,8 +32,8 @@
get() = {
setup {
tapl.setEnableRotation(true)
- setRotation(testSpec.startRotation)
- tapl.setExpectedRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
tapl.workspace.switchToOverview().dismissAllTasks()
}
teardown {
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 4a3284e..f3927d4 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,8 +41,8 @@
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
+import org.junit.Assert.assertNotNull
internal object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
@@ -129,10 +129,18 @@
// Find the second task in the upper right corner in split select mode by sorting
// 'left' in descending order and 'top' in ascending order.
- Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
- t2.getVisibleBounds().left - t1.getVisibleBounds().left})
- Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
- t1.getVisibleBounds().top - t2.getVisibleBounds().top})
+ Collections.sort(
+ snapshots,
+ { t1: UiObject2, t2: UiObject2 ->
+ t2.getVisibleBounds().left - t1.getVisibleBounds().left
+ }
+ )
+ Collections.sort(
+ snapshots,
+ { t1: UiObject2, t2: UiObject2 ->
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
+ )
snapshots[0].click()
} else {
tapl.workspace
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index f7610c4..09568b2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -19,13 +19,13 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -50,19 +50,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchAppByDoubleTapDivider(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
- setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
transitions {
SplitScreenUtils.doubleTapDividerToSwitch(device)
- wmHelper.StateSyncBuilder()
- .withAppTransitionIdle()
- .waitForAndVerify()
+ wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
waitForLayersToSwitch(wmHelper)
waitForWindowsToSwitch(wmHelper)
@@ -70,61 +66,74 @@
}
private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) {
- wmHelper.StateSyncBuilder().add("appWindowsSwitched") {
- val primaryAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
- primaryApp.windowMatchesAnyOf(window)
- } ?: return@add false
- val secondaryAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
- secondaryApp.windowMatchesAnyOf(window)
- } ?: return@add false
+ wmHelper
+ .StateSyncBuilder()
+ .add("appWindowsSwitched") {
+ val primaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ primaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppWindow =
+ it.wmState.visibleWindows.firstOrNull { window ->
+ secondaryApp.windowMatchesAnyOf(window)
+ }
+ ?: return@add false
- if (isLandscape(testSpec.endRotation)) {
- return@add if (testSpec.isTablet) {
- secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryAppWindow.frame.right <= primaryAppWindow.frame.left
+ } else {
+ primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
+ }
} else {
- primaryAppWindow.frame.right <= secondaryAppWindow.frame.left
- }
- } else {
- return@add if (testSpec.isTablet) {
- primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
- } else {
- primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ return@add if (flicker.scenario.isTablet) {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ } else {
+ primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top
+ }
}
}
- }.waitForAndVerify()
+ .waitForAndVerify()
}
private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) {
- wmHelper.StateSyncBuilder().add("appLayersSwitched") {
- val primaryAppLayer = it.layerState.visibleLayers.firstOrNull { window ->
- primaryApp.layerMatchesAnyOf(window)
- } ?: return@add false
- val secondaryAppLayer = it.layerState.visibleLayers.firstOrNull { window ->
- secondaryApp.layerMatchesAnyOf(window)
- } ?: return@add false
+ wmHelper
+ .StateSyncBuilder()
+ .add("appLayersSwitched") {
+ val primaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ primaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
+ val secondaryAppLayer =
+ it.layerState.visibleLayers.firstOrNull { window ->
+ secondaryApp.layerMatchesAnyOf(window)
+ }
+ ?: return@add false
- val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds
- ?: return@add false
- val secondaryVisibleRegion = secondaryAppLayer.visibleRegion?.bounds
- ?: return@add false
+ val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false
+ val secondaryVisibleRegion =
+ secondaryAppLayer.visibleRegion?.bounds ?: return@add false
- if (isLandscape(testSpec.endRotation)) {
- return@add if (testSpec.isTablet) {
- secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ if (isLandscape(flicker.scenario.endRotation)) {
+ return@add if (flicker.scenario.isTablet) {
+ secondaryVisibleRegion.right <= primaryVisibleRegion.left
+ } else {
+ primaryVisibleRegion.right <= secondaryVisibleRegion.left
+ }
} else {
- primaryVisibleRegion.right <= secondaryVisibleRegion.left
- }
- } else {
- return@add if (testSpec.isTablet) {
- primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
- } else {
- primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ return@add if (flicker.scenario.isTablet) {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ } else {
+ primaryVisibleRegion.bottom <= secondaryVisibleRegion.top
+ }
}
}
- }.waitForAndVerify()
+ .waitForAndVerify()
}
- private fun isLandscape(rotation: Int): Boolean {
+ private fun isLandscape(rotation: PlatformConsts.Rotation): Boolean {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return displayBounds.width > displayBounds.height
}
@@ -133,13 +142,13 @@
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(primaryApp)
- testSpec.appWindowIsVisibleAtStart(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(primaryApp)
+ flicker.appWindowIsVisibleAtStart(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(secondaryApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
// TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
// robust enough to get the correct end state.
@@ -147,63 +156,57 @@
@Presubmit
@Test
- fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+
+ @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+ fun secondaryAppLayerIsVisibleAtEnd() = flicker.layerIsVisibleAtEnd(secondaryApp)
@Presubmit
@Test
- fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
+ fun primaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp,
+ landscapePosLeft = !tapl.isTablet,
+ portraitPosTop = true
+ )
@Presubmit
@Test
- fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- primaryApp,
- landscapePosLeft = !tapl.isTablet,
- portraitPosTop = true
- )
+ fun secondaryAppBoundsIsVisibleAtEnd() =
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp,
+ landscapePosLeft = tapl.isTablet,
+ portraitPosTop = false
+ )
@Presubmit
@Test
- fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
- secondaryApp,
- landscapePosLeft = tapl.isTablet,
- portraitPosTop = false
- )
+ fun primaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(primaryApp)
@Presubmit
@Test
- fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ fun secondaryAppWindowIsVisibleAtEnd() = flicker.appWindowIsVisibleAtEnd(secondaryApp)
- @Presubmit
- @Test
- fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ /** {@inheritDoc} */
+ @Postsubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
-
- /** {@inheritDoc} */
- @Postsubmit
- @Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -214,26 +217,22 @@
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Postsubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Postsubmit
@@ -250,11 +249,10 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
// TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 993dba2..940e0e9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,7 +45,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromAnotherApp(flicker: FlickerTest) : SplitScreenBase(flicker) {
val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation)
override val transition: FlickerBuilder.() -> Unit
@@ -66,22 +66,22 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -90,7 +90,7 @@
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -98,17 +98,14 @@
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -166,13 +163,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 2a552cd..85812c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,7 +45,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromHome(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -65,22 +65,22 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -89,7 +89,7 @@
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -97,17 +97,14 @@
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -165,13 +162,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index 7f81bae..7c62433 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
@@ -45,7 +45,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBackToSplitFromRecent(flicker: FlickerTest) : SplitScreenBase(flicker) {
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -65,22 +65,22 @@
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
@Presubmit
@Test
- fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+ fun splitScreenDividerBecomesVisible() = flicker.splitScreenDividerBecomesVisible()
- @Presubmit @Test fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ @Presubmit @Test fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -89,7 +89,7 @@
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -97,17 +97,14 @@
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
/** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@@ -165,13 +162,11 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
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 f5f5fd8..193ab98 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
@@ -20,22 +20,22 @@
import android.platform.test.annotations.IwTest
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
-import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
-import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,7 +51,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+class SwitchBetweenSplitPairs(flicker: FlickerTest) : SplitScreenBase(flicker) {
private val thirdApp = SplitScreenUtils.getIme(instrumentation)
private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
@@ -77,21 +77,21 @@
@Presubmit
@Test
fun cujCompleted() {
- testSpec.appWindowIsVisibleAtStart(thirdApp)
- testSpec.appWindowIsVisibleAtStart(fourthApp)
- testSpec.splitScreenDividerIsVisibleAtStart()
+ flicker.appWindowIsVisibleAtStart(thirdApp)
+ flicker.appWindowIsVisibleAtStart(fourthApp)
+ flicker.splitScreenDividerIsVisibleAtStart()
- testSpec.appWindowIsVisibleAtEnd(primaryApp)
- testSpec.appWindowIsVisibleAtEnd(secondaryApp)
- testSpec.appWindowIsInvisibleAtEnd(thirdApp)
- testSpec.appWindowIsInvisibleAtEnd(fourthApp)
- testSpec.splitScreenDividerIsVisibleAtEnd()
+ flicker.appWindowIsVisibleAtEnd(primaryApp)
+ flicker.appWindowIsVisibleAtEnd(secondaryApp)
+ flicker.appWindowIsInvisibleAtEnd(thirdApp)
+ flicker.appWindowIsInvisibleAtEnd(fourthApp)
+ flicker.splitScreenDividerIsVisibleAtEnd()
}
@Presubmit
@Test
fun splitScreenDividerInvisibleAtMiddle() =
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
.then()
.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
@@ -101,24 +101,24 @@
@FlakyTest(bugId = 247095572)
@Test
- fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+ fun primaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(primaryApp)
@FlakyTest(bugId = 247095572)
@Test
- fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+ fun secondaryAppLayerBecomesVisible() = flicker.layerBecomesVisible(secondaryApp)
@FlakyTest(bugId = 247095572)
@Test
- fun thirdAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(thirdApp)
+ fun thirdAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(thirdApp)
@FlakyTest(bugId = 247095572)
@Test
- fun fourthAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(fourthApp)
+ fun fourthAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(fourthApp)
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false
@@ -127,7 +127,7 @@
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() =
- testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ flicker.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true
@@ -136,66 +136,62 @@
@Presubmit
@Test
fun thirdAppBoundsIsVisibleAtBegin() =
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.splitAppLayerBoundsSnapToDivider(
thirdApp,
landscapePosLeft = tapl.isTablet,
portraitPosTop = false,
- testSpec.startRotation
+ flicker.scenario.startRotation
)
}
@Presubmit
@Test
fun fourthAppBoundsIsVisibleAtBegin() =
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.splitAppLayerBoundsSnapToDivider(
fourthApp,
landscapePosLeft = !tapl.isTablet,
portraitPosTop = true,
- testSpec.startRotation
+ flicker.scenario.startRotation
)
}
@Presubmit
@Test
- fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+ fun primaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(primaryApp)
@Presubmit
@Test
- fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+ fun secondaryAppWindowBecomesVisible() = flicker.appWindowBecomesVisible(secondaryApp)
@Presubmit
@Test
- fun thirdAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(thirdApp)
+ fun thirdAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(thirdApp)
@Presubmit
@Test
- fun fourthAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(fourthApp)
+ fun fourthAppWindowBecomesVisible() = flicker.appWindowBecomesInvisible(fourthApp)
/** {@inheritDoc} */
@FlakyTest(bugId = 251268711)
@Test
- override fun entireScreenCovered() =
- super.entireScreenCovered()
+ override fun entireScreenCovered() = super.entireScreenCovered()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun navBarLayerPositionAtStartAndEnd() =
- super.navBarLayerPositionAtStartAndEnd()
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun navBarWindowIsAlwaysVisible() =
- super.navBarWindowIsAlwaysVisible()
+ override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@@ -206,26 +202,22 @@
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarLayerPositionAtStartAndEnd() =
- super.statusBarLayerPositionAtStartAndEnd()
+ override fun statusBarLayerPositionAtStartAndEnd() = super.statusBarLayerPositionAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun statusBarWindowIsAlwaysVisible() =
- super.statusBarWindowIsAlwaysVisible()
+ override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarLayerIsVisibleAtStartAndEnd() =
- super.taskBarLayerIsVisibleAtStartAndEnd()
+ override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
/** {@inheritDoc} */
@Presubmit
@Test
- override fun taskBarWindowIsAlwaysVisible() =
- super.taskBarWindowIsAlwaysVisible()
+ override fun taskBarWindowIsAlwaysVisible() = super.taskBarWindowIsAlwaysVisible()
/** {@inheritDoc} */
@FlakyTest
@@ -242,8 +234,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
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 bee9a90..8a5b490 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
@@ -53,6 +53,7 @@
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
@@ -223,9 +224,10 @@
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- verify(mAnimatorCallback).onBackStarted(any(BackEvent.class));
+ verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class));
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
// Check that back invocation is dispatched.
@@ -246,7 +248,8 @@
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
- ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+ ArgumentCaptor<BackMotionEvent> backEventCaptor =
+ ArgumentCaptor.forClass(BackMotionEvent.class);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index 3aefc3f..ba9c159 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import android.window.BackEvent;
+import android.window.BackMotionEvent;
import org.junit.Before;
import org.junit.Test;
@@ -38,7 +39,7 @@
@Test
public void generatesProgress_onStart() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
- BackEvent event = mTouchTracker.createStartEvent(null);
+ BackMotionEvent event = mTouchTracker.createStartEvent(null);
assertEquals(event.getProgress(), 0f, 0f);
}
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 89bafcb..dad9133 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
@@ -36,7 +36,7 @@
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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -50,6 +50,7 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
import androidx.test.filters.SmallTest;
@@ -99,15 +100,14 @@
@Before
public void setUp() {
mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+ when(DesktopModeStatus.isSupported()).thenReturn(true);
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
- mController = new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
- mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+ mController = createController();
when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
@@ -124,7 +124,17 @@
@Test
public void instantiate_addInitCallback() {
- verify(mShellInit, times(1)).addInitCallback(any(), any());
+ verify(mShellInit).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void instantiate_flagOff_doNotAddInitCallback() {
+ when(DesktopModeStatus.isSupported()).thenReturn(false);
+ clearInvocations(mShellInit);
+
+ createController();
+
+ verify(mShellInit, never()).addInitCallback(any(), any());
}
@Test
@@ -222,25 +232,29 @@
// 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);
+ 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);
+ 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
+ public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+ // Set up two active tasks on desktop, task2 is on top of task1.
RunningTaskInfo freeformTask1 = createFreeformTask();
- freeformTask1.lastActiveTime = 100;
- RunningTaskInfo freeformTask2 = createFreeformTask();
- freeformTask2.lastActiveTime = 200;
mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask1.taskId, false /* visible */);
+ RunningTaskInfo freeformTask2 = createFreeformTask();
mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+ freeformTask2.taskId, false /* visible */);
when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
freeformTask1);
when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
@@ -248,27 +262,66 @@
// Run show desktop apps logic
mController.showDesktopApps();
- ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- 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();
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
// Check wct has reorder calls
assertThat(wct.getHierarchyOps()).hasSize(2);
- // Task 2 has activity later, must be first
- WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ // Task 1 appeared first, must be first reorder to top.
+ HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
- // Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ // Task 2 appeared last, must be last reorder to top.
+ HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+ }
+
+ @Test
+ public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // No reordering needed.
+ assertThat(wct.getHierarchyOps()).isEmpty();
+ }
+
+ @Test
+ public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+ final RunningTaskInfo task1 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+ final RunningTaskInfo task2 = createFreeformTask();
+ mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+ mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+ mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+ when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+ mController.showDesktopApps();
+
+ final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+ // Both tasks should be reordered to top, even if one was already visible.
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+ final HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
}
@Test
@@ -309,6 +362,12 @@
assertThat(wct).isNotNull();
}
+ private DesktopModeController createController() {
+ return new DesktopModeController(mContext, mShellInit, mShellController,
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+ }
+
private DisplayAreaInfo createMockDisplayArea() {
DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
mContext.getDisplayId(), 0);
@@ -355,6 +414,17 @@
return arg.getValue();
}
+ private WindowContainerTransaction getBringAppsToFrontTransaction() {
+ final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).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();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index aaa5c8a..1e43a59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,32 @@
assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
}
+ @Test
+ fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks[0]).isEqualTo(7)
+ assertThat(tasks[1]).isEqualTo(6)
+ assertThat(tasks[2]).isEqualTo(5)
+ }
+
+ @Test
+ fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+ repo.addOrMoveFreeformTaskToTop(5)
+ repo.addOrMoveFreeformTaskToTop(6)
+ repo.addOrMoveFreeformTaskToTop(7)
+
+ repo.addOrMoveFreeformTaskToTop(6)
+
+ val tasks = repo.getFreeformTasksInZOrder()
+ assertThat(tasks.size).isEqualTo(3)
+ assertThat(tasks.first()).isEqualTo(6)
+ }
+
class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
var activeTaskChangedCalls = 0
override fun onActiveTasksChanged() {
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 38b75f8..f8ded77 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
@@ -178,10 +178,10 @@
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Put the same component into focus task
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
@@ -199,10 +199,10 @@
Intent startIntent = createStartIntent("startActivity");
PendingIntent pendingIntent =
PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Put the same component into focus task
- ActivityManager.RunningTaskInfo focusTaskInfo =
+ // Put the same component to the top running task
+ ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index 8b134ed..ad6fced 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -54,6 +54,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.function.Supplier;
/** Tests of {@link CaptionWindowDecorViewModel} */
@@ -101,7 +102,7 @@
mTaskOrganizer,
mDisplayController,
mSyncQueue,
- mDesktopModeController,
+ Optional.of(mDesktopModeController),
mCaptionWindowDecorFactory,
new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
@@ -111,7 +112,7 @@
.create(any(), any(), any(), any(), any(), any(), any(), any());
when(mInputManager.monitorGestureInput(any(), anyInt())).thenReturn(mInputMonitor);
- when(mEventReceiverFactory.create(any(), any())).thenReturn(mEventReceiver);
+ when(mEventReceiverFactory.create(any(), any(), any())).thenReturn(mEventReceiver);
when(mInputMonitor.getInputChannel()).thenReturn(mInputChannel);
}
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
new file mode 100644
index 0000000..506128e
--- /dev/null
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -0,0 +1,175 @@
+/*
+ * 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.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.location.Location;
+
+import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.S2CellIdUtils;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+
+/**
+ * Converts altitudes reported above the World Geodetic System 1984 (WGS84) reference ellipsoid
+ * into ones above Mean Sea Level.
+ */
+public final class AltitudeConverter {
+
+ private static final double MAX_ABS_VALID_LATITUDE = 90;
+ private static final double MAX_ABS_VALID_LONGITUDE = 180;
+
+ /** Manages a mapping of geoid heights associated with S2 cells. */
+ private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+
+ /**
+ * Creates an instance that manages an independent cache to optimized conversions of locations
+ * in proximity to one another.
+ */
+ public AltitudeConverter() {
+ }
+
+ /**
+ * Throws an {@link IllegalArgumentException} if the {@code location} has an invalid latitude,
+ * longitude, or altitude above WGS84.
+ */
+ private static void validate(@NonNull Location location) {
+ Preconditions.checkArgument(
+ isFiniteAndAtAbsMost(location.getLatitude(), MAX_ABS_VALID_LATITUDE),
+ "Invalid latitude: %f", location.getLatitude());
+ Preconditions.checkArgument(
+ isFiniteAndAtAbsMost(location.getLongitude(), MAX_ABS_VALID_LONGITUDE),
+ "Invalid longitude: %f", location.getLongitude());
+ Preconditions.checkArgument(location.hasAltitude(), "Missing altitude above WGS84");
+ Preconditions.checkArgument(Double.isFinite(location.getAltitude()),
+ "Invalid altitude above WGS84: %f", location.getAltitude());
+ }
+
+ private static boolean isFiniteAndAtAbsMost(double value, double rhs) {
+ return Double.isFinite(value) && Math.abs(value) <= rhs;
+ }
+
+ /**
+ * Returns the four S2 cell IDs for the map square associated with the {@code location}.
+ *
+ * <p>The first map cell contains the location, while the others are located horizontally,
+ * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
+ * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
+ * corresponding ID is set to zero.
+ */
+ @NonNull
+ private static long[] findMapSquare(@NonNull MapParamsProto params,
+ @NonNull Location location) {
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+
+ // (0,0) cell.
+ long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
+ long[] edgeNeighbors = new long[4];
+ S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
+
+ // (1,0) cell.
+ int i1 = S2CellIdUtils.getI(s2CellId) > S2CellIdUtils.getI(s0) ? -1 : 1;
+ long s1 = edgeNeighbors[i1 + 2];
+
+ // (0,1) cell.
+ int i2 = S2CellIdUtils.getJ(s2CellId) > S2CellIdUtils.getJ(s0) ? 1 : -1;
+ long s2 = edgeNeighbors[i2 + 1];
+
+ // (1,1) cell.
+ S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
+ long s3 = 0;
+ for (int i = 0; i < edgeNeighbors.length; i++) {
+ if (edgeNeighbors[i] == s0) {
+ int i3 = (i + i1 * i2 + edgeNeighbors.length) % edgeNeighbors.length;
+ s3 = edgeNeighbors[i3] == s2 ? 0 : edgeNeighbors[i3];
+ break;
+ }
+ }
+
+ // Reuse edge neighbors' array to avoid an extra allocation.
+ edgeNeighbors[0] = s0;
+ edgeNeighbors[1] = s1;
+ edgeNeighbors[2] = s2;
+ edgeNeighbors[3] = s3;
+ return edgeNeighbors;
+ }
+
+ /**
+ * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
+ * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
+ * accuracy; otherwise, does not add a corresponding accuracy.
+ */
+ private static void addMslAltitude(@NonNull MapParamsProto params, @NonNull long[] s2CellIds,
+ @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
+ long s0 = s2CellIds[0];
+ double h0 = geoidHeightsMeters[0];
+ double h1 = geoidHeightsMeters[1];
+ double h2 = geoidHeightsMeters[2];
+ double h3 = s2CellIds[3] == 0 ? h0 : geoidHeightsMeters[3];
+
+ // Bilinear interpolation on an S2 square of size equal to that of a map cell. wi and wj
+ // are the normalized [0,1] weights in the i and j directions, respectively, allowing us to
+ // employ the simplified unit square formulation.
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+ double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ double wi = Math.abs(S2CellIdUtils.getI(s2CellId) - S2CellIdUtils.getI(s0)) / sizeIj;
+ double wj = Math.abs(S2CellIdUtils.getJ(s2CellId) - S2CellIdUtils.getJ(s0)) / sizeIj;
+ double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
+
+ location.setMslAltitudeMeters(location.getAltitude() - offsetMeters);
+ if (location.hasVerticalAccuracy()) {
+ double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
+ if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
+ location.setMslAltitudeAccuracyMeters(
+ (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+ }
+ }
+ }
+
+ /**
+ * Adds a Mean Sea Level altitude to the {@code location}. In addition, adds a Mean Sea Level
+ * altitude accuracy if the {@code location} has a finite and non-negative vertical accuracy;
+ * otherwise, does not add a corresponding accuracy.
+ *
+ * <p>Must be called off the main thread as data may be loaded from raw assets. Throws an
+ * {@link IOException} if an I/O error occurs when loading data.
+ *
+ * <p>Throws an {@link IllegalArgumentException} if the {@code location} has an invalid
+ * latitude, longitude, or altitude above WGS84. Specifically:
+ *
+ * <ul>
+ * <li>The latitude must be between -90 and 90, both inclusive.
+ * <li>The longitude must be between -180 and 180, both inclusive.
+ * <li>The altitude above WGS84 must be finite.
+ * </ul>
+ */
+ @WorkerThread
+ public void addMslAltitude(@NonNull Context context, @NonNull Location location)
+ throws IOException {
+ validate(location);
+ MapParamsProto params = GeoidHeightMap.getParams(context);
+ long[] s2CellIds = findMapSquare(params, location);
+ double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
+ addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+ }
+}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
new file mode 100644
index 0000000..6430eb4
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
@@ -0,0 +1,367 @@
+/*
+ * 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.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ *
+ * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
+ * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
+ * level.
+ *
+ * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
+ * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
+ * its common parent's S2 cell ID, referred to as a CACHE KEY.
+ *
+ * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
+ * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
+ * referred to as a DISK TOKEN.
+ */
+public final class GeoidHeightMap {
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ @Nullable
+ private static MapParamsProto sParams;
+
+ /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
+ private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+
+ /**
+ * Returns the singleton parameter instance for a spherically projected geoid height map and its
+ * corresponding tile management.
+ */
+ @NonNull
+ public static MapParamsProto getParams(@NonNull Context context) throws IOException {
+ synchronized (sLock) {
+ if (sParams == null) {
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/map-params.pb")) {
+ sParams = MapParamsProto.parseFrom(is.readAllBytes());
+ }
+ }
+ return sParams;
+ }
+ }
+
+ private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
+ return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
+ }
+
+ @NonNull
+ private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
+ return S2CellIdUtils.getToken(
+ S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+ }
+
+ /**
+ * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
+ * {@code s2CellIds}. Returns true if values are present for all non-zero IDs; otherwise,
+ * returns false and adds NaNs for absent values.
+ */
+ private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
+ @NonNull TileFunction tileFunction,
+ @NonNull long[] s2CellIds, @NonNull double[] values) {
+ int len = s2CellIds.length;
+
+ S2TileProto[] tiles = new S2TileProto[len];
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] != 0) {
+ tiles[i] = tileFunction.getTile(s2CellIds[i]);
+ }
+ values[i] = Double.NaN;
+ }
+
+ for (int i = 0; i < len; i++) {
+ if (tiles[i] == null || !Double.isNaN(values[i])) {
+ continue;
+ }
+
+ mergeByteBufferValues(params, s2CellIds, tiles, i, values);
+ mergeByteJpegValues(params, s2CellIds, tiles, i, values);
+ mergeBytePngValues(params, s2CellIds, tiles, i, values);
+ }
+
+ boolean allFound = true;
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0) {
+ continue;
+ }
+ if (Double.isNaN(values[i])) {
+ allFound = false;
+ } else {
+ values[i] = (((int) values[i]) & 0xFF) / 255.0;
+ }
+ }
+ return allFound;
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private static void mergeByteBufferValues(@NonNull MapParamsProto params,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles,
+ int tileIndex, @NonNull double[] values) {
+ byte[] bytes = tiles[tileIndex].byteBuffer;
+ if (bytes == null || bytes.length == 0) {
+ return;
+ }
+
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+ int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
+ int numBitsLeftOfTile = 2 * tileS2Level + 3;
+
+ for (int i = tileIndex; i < tiles.length; i++) {
+ if (tiles[i] != tiles[tileIndex]) {
+ continue;
+ }
+
+ long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
+ int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
+ int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
+ values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+ values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
+ }
+ }
+
+ private static void mergeByteJpegValues(@NonNull MapParamsProto params,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles,
+ int tileIndex, @NonNull double[] values) {
+ mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
+ values);
+ }
+
+ private static void mergeBytePngValues(@NonNull MapParamsProto params,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles,
+ int tileIndex, @NonNull double[] values) {
+ mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
+ }
+
+ @SuppressWarnings("ReferenceEquality")
+ private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
+ @NonNull long[] s2CellIds,
+ @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+ if (bytes == null || bytes.length == 0) {
+ return;
+ }
+ Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+ if (bitmap == null) {
+ return;
+ }
+
+ for (int i = tileIndex; i < tiles.length; i++) {
+ if (s2CellIds[i] == 0 || tiles[i] != tiles[tileIndex]) {
+ continue;
+ }
+
+ values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+ values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
+ getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
+ }
+ }
+
+ /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
+ private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
+ return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
+ }
+
+ /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
+ private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
+ return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
+ }
+
+ private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
+ return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
+ }
+
+ /**
+ * Returns the geoid heights in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a
+ * non-zero ID.
+ */
+ @NonNull
+ public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
+ @NonNull long[] s2CellIds) throws IOException {
+ Preconditions.checkArgument(s2CellIds.length == 4);
+ for (long s2CellId : s2CellIds) {
+ Preconditions.checkArgument(
+ s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+ }
+
+ double[] heightsMeters = new double[s2CellIds.length];
+ if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+ return heightsMeters;
+ }
+
+ TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
+ if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
+ return heightsMeters;
+ }
+ throw new IOException("Unable to calculate geoid heights from raw assets.");
+ }
+
+ /**
+ * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+ * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs;
+ * otherwise, returns false and adds NaNs for absent heights.
+ */
+ private boolean getGeoidHeights(@NonNull MapParamsProto params,
+ @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+ @NonNull double[] heightsMeters) {
+ boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
+ for (int i = 0; i < heightsMeters.length; i++) {
+ // NaNs are properly preserved.
+ heightsMeters[i] *= params.modelAMeters;
+ heightsMeters[i] += params.modelBMeters;
+ }
+ return allFound;
+ }
+
+ @NonNull
+ private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ int len = s2CellIds.length;
+
+ // Enable batch loading by finding all cache keys upfront.
+ long[] cacheKeys = new long[len];
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0) {
+ continue;
+ }
+ cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
+ }
+
+ // Attempt to load tiles from cache.
+ S2TileProto[] loadedTiles = new S2TileProto[len];
+ String[] diskTokens = new String[len];
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0 || diskTokens[i] != null) {
+ continue;
+ }
+ loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+ diskTokens[i] = getDiskToken(params, cacheKeys[i]);
+
+ // Batch across common cache key.
+ for (int j = i + 1; j < len; j++) {
+ if (cacheKeys[j] == cacheKeys[i]) {
+ loadedTiles[j] = loadedTiles[i];
+ diskTokens[j] = diskTokens[i];
+ }
+ }
+ }
+
+ // Attempt to load tiles from disk.
+ for (int i = 0; i < len; i++) {
+ if (s2CellIds[i] == 0 || loadedTiles[i] != null) {
+ continue;
+ }
+
+ S2TileProto tile;
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+ tile = S2TileProto.parseFrom(is.readAllBytes());
+ }
+ mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+ }
+
+ return s2CellId -> {
+ if (s2CellId == 0) {
+ return null;
+ }
+ long cacheKey = getCacheKey(params, s2CellId);
+ for (int i = 0; i < cacheKeys.length; i++) {
+ if (cacheKeys[i] == cacheKey) {
+ return loadedTiles[i];
+ }
+ }
+ return null;
+ };
+ }
+
+ private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
+ @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
+ @NonNull S2TileProto[] loadedTiles) throws IOException {
+ int len = cacheKeys.length;
+ int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
+
+ // Reusable arrays.
+ long[] s2CellIds = new long[numMapCellsPerCacheTile];
+ double[] values = new double[numMapCellsPerCacheTile];
+
+ // Each cache key identifies a different sub-tile of the disk tile.
+ TileFunction diskTileFunction = s2CellId -> diskTile;
+ for (int i = diskTokenIndex; i < len; i++) {
+ if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
+ || loadedTiles[i] != null) {
+ continue;
+ }
+
+ // Find all map cells within the current cache tile.
+ long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
+ for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+ s2CellIds[j] = s2CellId;
+ s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
+ }
+
+ if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
+ throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
+ }
+
+ loadedTiles[i] = new S2TileProto();
+ loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
+ for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+ loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
+ }
+
+ // Batch across common cache key.
+ for (int j = i + 1; j < len; j++) {
+ if (cacheKeys[j] == cacheKeys[i]) {
+ loadedTiles[j] = loadedTiles[i];
+ }
+ }
+
+ // Side load into tile cache.
+ mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+ }
+ }
+
+ /** Defines a function-like object to retrieve tiles for map cells. */
+ private interface TileFunction {
+
+ @Nullable
+ S2TileProto getTile(long s2CellId);
+ }
+}
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
new file mode 100644
index 0000000..5f11387
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
@@ -0,0 +1,653 @@
+/*
+ * 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.location.altitude;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
+ *
+ * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
+ */
+public final class S2CellIdUtils {
+
+ /** The level of all leaf S2 cells. */
+ public static final int MAX_LEVEL = 30;
+
+ private static final int MAX_SIZE = 1 << MAX_LEVEL;
+ private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
+ private static final int NUM_FACES = 6;
+ private static final int POS_BITS = 2 * MAX_LEVEL + 1;
+ private static final int SWAP_MASK = 0x1;
+ private static final int LOOKUP_BITS = 4;
+ private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
+ private static final int INVERT_MASK = 0x2;
+ private static final int LEAF_MASK = 0x1;
+ private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
+ private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
+ private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
+ private static final int[][] POS_TO_IJ =
+ {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
+ private static final double UV_LIMIT = calculateUvLimit();
+ private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
+ private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
+
+ // Used to encode (i, j, o) coordinates into primitive longs.
+ private static final int I_SHIFT = 33;
+ private static final int J_SHIFT = 2;
+ private static final long J_MASK = (1L << 31) - 1;
+
+ static {
+ initLookupCells();
+ }
+
+ /** Prevents instantiation. */
+ private S2CellIdUtils() {
+ }
+
+ /**
+ * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+ * degrees.
+ */
+ public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
+ return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
+ }
+
+ /**
+ * Returns the ID of the parent of the specified S2 cell at the specified parent level.
+ * Behavior is undefined for invalid S2 cell IDs or parent levels not in
+ * [0, {@code getLevel(s2CellId)}[.
+ */
+ public static long getParent(long s2CellId, int level) {
+ long newLsb = getLowestOnBitForLevel(level);
+ return (s2CellId & -newLsb) | newLsb;
+ }
+
+ /**
+ * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
+ * cells adjacent across the specified cell's four edges. This array must be of minimum
+ * length four, and elements at the tail end of the array not corresponding to a neighbor
+ * are set to zero. A reference to this array is returned.
+ *
+ * <p>Inserts in the order of down, right, up, and left directions, in that order. All
+ * neighbors are guaranteed to be distinct.
+ */
+ public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
+ int level = getLevel(s2CellId);
+ int size = levelToSizeIj(level);
+ int face = getFace(s2CellId);
+ long ijo = toIjo(s2CellId);
+ int i = ijoToI(ijo);
+ int j = ijoToJ(ijo);
+
+ int iPlusSize = i + size;
+ int iMinusSize = i - size;
+ int jPlusSize = j + size;
+ int jMinusSize = j - size;
+ boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
+ boolean iMinusSizeGteZero = iMinusSize >= 0;
+ boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
+ boolean jMinusSizeGteZero = jMinusSize >= 0;
+
+ int index = 0;
+ // Down direction.
+ neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
+ level);
+ // Right direction.
+ neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
+ // Up direction.
+ neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
+ // Left direction.
+ neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
+ level);
+
+ // Pad end of neighbor array with zeros.
+ Arrays.fill(neighbors, index, neighbors.length, 0);
+ }
+
+ /** Returns the "i" coordinate for the specified S2 cell. */
+ public static int getI(long s2CellId) {
+ return ijoToI(toIjo(s2CellId));
+ }
+
+ /** Returns the "j" coordinate for the specified S2 cell. */
+ public static int getJ(long s2CellId) {
+ return ijoToJ(toIjo(s2CellId));
+ }
+
+ /**
+ * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+ * radians.
+ */
+ private static long fromLatLngRadians(double latRadians, double lngRadians) {
+ double cosLat = Math.cos(latRadians);
+ double x = Math.cos(lngRadians) * cosLat;
+ double y = Math.sin(lngRadians) * cosLat;
+ double z = Math.sin(latRadians);
+ return fromXyz(x, y, z);
+ }
+
+ /**
+ * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
+ * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
+ */
+ static int getLevel(long s2CellId) {
+ if (isLeaf(s2CellId)) {
+ return MAX_LEVEL;
+ }
+ return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
+ }
+
+ /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
+ static long getLowestOnBit(long s2CellId) {
+ return s2CellId & -s2CellId;
+ }
+
+ /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
+ static long getLowestOnBitForLevel(int level) {
+ return 1L << (2 * (MAX_LEVEL - level));
+ }
+
+ /**
+ * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
+ * level, in Hilbert curve order.
+ */
+ static long getTraversalStart(long s2CellId, int level) {
+ return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
+ }
+
+ /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
+ static long getTraversalNext(long s2CellId) {
+ return s2CellId + (getLowestOnBit(s2CellId) << 1);
+ }
+
+ /**
+ * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
+ * lower levels (i.e., larger cells) are encoded into fewer characters.
+ */
+ @NonNull
+ static String getToken(long s2CellId) {
+ if (s2CellId == 0) {
+ return "X";
+ }
+
+ // Convert to a hex string with as many digits as necessary.
+ String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
+ // Prefix 0s to get a length 16 string.
+ String padded = padStart(hex);
+ // Trim zeroes off the end.
+ return padded.replaceAll("0*$", "");
+ }
+
+ private static String padStart(String string) {
+ if (string.length() >= 16) {
+ return string;
+ }
+ return "0".repeat(16 - string.length()) + string;
+ }
+
+ /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
+ private static long fromXyz(double x, double y, double z) {
+ int face = xyzToFace(x, y, z);
+ UvTransform uvTransform = UV_TRANSFORMS[face];
+ double u = uvTransform.xyzToU(x, y, z);
+ double v = uvTransform.xyzToV(x, y, z);
+ return fromFuv(face, u, v);
+ }
+
+ /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
+ private static long fromFuv(int face, double u, double v) {
+ int i = uToI(u);
+ int j = vToJ(v);
+ return fromFij(face, i, j);
+ }
+
+ /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+ private static long fromFij(int face, int i, int j) {
+ int bits = (face & SWAP_MASK);
+ // Update most significant bits.
+ long msb = ((long) face) << (POS_BITS - 33);
+ for (int k = 7; k >= 4; --k) {
+ bits = lookupBits(i, j, k, bits);
+ msb = updateBits(msb, k, bits);
+ bits = maskBits(bits);
+ }
+ // Update least significant bits.
+ long lsb = 0;
+ for (int k = 3; k >= 0; --k) {
+ bits = lookupBits(i, j, k, bits);
+ lsb = updateBits(lsb, k, bits);
+ bits = maskBits(bits);
+ }
+ return (((msb << 32) + lsb) << 1) + 1;
+ }
+
+ private static long fromFijWrap(int face, int i, int j) {
+ double u = iToU(i);
+ double v = jToV(j);
+
+ XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
+ double x = xyzTransform.uvToX(u, v);
+ double y = xyzTransform.uvToY(u, v);
+ double z = xyzTransform.uvToZ(u, v);
+
+ int newFace = xyzToFace(x, y, z);
+ UvTransform uvTransform = UV_TRANSFORMS[newFace];
+ double newU = uvTransform.xyzToU(x, y, z);
+ double newV = uvTransform.xyzToV(x, y, z);
+
+ int newI = uShiftIntoI(newU);
+ int newJ = vShiftIntoJ(newV);
+ return fromFij(newFace, newI, newJ);
+ }
+
+ private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
+ if (isSameFace) {
+ return fromFij(face, i, j);
+ }
+ return fromFijWrap(face, i, j);
+ }
+
+ /**
+ * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
+ * on a face boundary, the returned face is arbitrary but repeatable.
+ */
+ private static int xyzToFace(double x, double y, double z) {
+ double absX = Math.abs(x);
+ double absY = Math.abs(y);
+ double absZ = Math.abs(z);
+ if (absX > absY) {
+ if (absX > absZ) {
+ return (x < 0) ? 3 : 0;
+ }
+ return (z < 0) ? 5 : 2;
+ }
+ if (absY > absZ) {
+ return (y < 0) ? 4 : 1;
+ }
+ return (z < 0) ? 5 : 2;
+ }
+
+ private static int uToI(double u) {
+ double s;
+ if (u >= 0) {
+ s = 0.5 * Math.sqrt(1 + 3 * u);
+ } else {
+ s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
+ }
+ return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+ }
+
+ private static int vToJ(double v) {
+ // Same calculation as uToI.
+ return uToI(v);
+ }
+
+ private static int lookupBits(int i, int j, int k, int bits) {
+ bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
+ bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
+ return LOOKUP_POS[bits];
+ }
+
+ private static long updateBits(long sb, int k, int bits) {
+ return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
+ }
+
+ private static int maskBits(int bits) {
+ return bits & (SWAP_MASK | INVERT_MASK);
+ }
+
+ private static int getFace(long s2CellId) {
+ return (int) (s2CellId >>> POS_BITS);
+ }
+
+ private static boolean isLeaf(long s2CellId) {
+ return ((int) s2CellId & LEAF_MASK) != 0;
+ }
+
+ private static double iToU(int i) {
+ int satI = Math.max(-1, Math.min(MAX_SIZE, i));
+ return Math.max(
+ -UV_LIMIT,
+ Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
+ }
+
+ private static double jToV(int j) {
+ // Same calculation as iToU.
+ return iToU(j);
+ }
+
+ private static long toIjo(long s2CellId) {
+ int face = getFace(s2CellId);
+ int bits = face & SWAP_MASK;
+ int i = 0;
+ int j = 0;
+ for (int k = 7; k >= 0; --k) {
+ int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
+ bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
+ - 1)) << 2;
+ bits = LOOKUP_IJ[bits];
+ i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
+ j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
+ bits &= (SWAP_MASK | INVERT_MASK);
+ }
+ int orientation =
+ ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
+ : bits;
+ return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
+ }
+
+ private static int ijoToI(long ijo) {
+ return (int) (ijo >>> I_SHIFT);
+ }
+
+ private static int ijoToJ(long ijo) {
+ return (int) ((ijo >>> J_SHIFT) & J_MASK);
+ }
+
+ private static int uShiftIntoI(double u) {
+ double s = 0.5 * (u + 1);
+ return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+ }
+
+ private static int vShiftIntoJ(double v) {
+ // Same calculation as uShiftIntoI.
+ return uShiftIntoI(v);
+ }
+
+ private static int levelToSizeIj(int level) {
+ return 1 << (MAX_LEVEL - level);
+ }
+
+ private static void initLookupCells() {
+ initLookupCell(0, 0, 0, 0, 0, 0);
+ initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
+ initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
+ initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
+ }
+
+ private static void initLookupCell(
+ int level, int i, int j, int origOrientation, int pos, int orientation) {
+ if (level == LOOKUP_BITS) {
+ int ij = (i << LOOKUP_BITS) + j;
+ LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
+ LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
+ } else {
+ level++;
+ i <<= 1;
+ j <<= 1;
+ pos <<= 2;
+ for (int subPos = 0; subPos < 4; subPos++) {
+ int ij = POS_TO_IJ[orientation][subPos];
+ int orientationMask = POS_TO_ORIENTATION[subPos];
+ initLookupCell(
+ level,
+ i + (ij >>> 1),
+ j + (ij & 0x1),
+ origOrientation,
+ pos + subPos,
+ orientation ^ orientationMask);
+ }
+ }
+ }
+
+ private static double calculateUvLimit() {
+ double machEps = 1.0;
+ do {
+ machEps /= 2.0f;
+ } while ((1.0 + (machEps / 2.0)) != 1.0);
+ return 1.0 + machEps;
+ }
+
+ @NonNull
+ private static UvTransform[] createUvTransforms() {
+ UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
+ uvTransforms[0] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return y / x;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return z / x;
+ }
+ };
+ uvTransforms[1] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return -x / y;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return z / y;
+ }
+ };
+ uvTransforms[2] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return -x / z;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return -y / z;
+ }
+ };
+ uvTransforms[3] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return z / x;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return y / x;
+ }
+ };
+ uvTransforms[4] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return z / y;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return -x / y;
+ }
+ };
+ uvTransforms[5] =
+ new UvTransform() {
+
+ @Override
+ public double xyzToU(double x, double y, double z) {
+ return -y / z;
+ }
+
+ @Override
+ public double xyzToV(double x, double y, double z) {
+ return -x / z;
+ }
+ };
+ return uvTransforms;
+ }
+
+ @NonNull
+ private static XyzTransform[] createXyzTransforms() {
+ XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
+ xyzTransforms[0] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return 1;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return u;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return v;
+ }
+ };
+ xyzTransforms[1] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return -u;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return 1;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return v;
+ }
+ };
+ xyzTransforms[2] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return -u;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return -v;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return 1;
+ }
+ };
+ xyzTransforms[3] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return -1;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return -v;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return -u;
+ }
+ };
+ xyzTransforms[4] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return v;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return -1;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return -u;
+ }
+ };
+ xyzTransforms[5] =
+ new XyzTransform() {
+
+ @Override
+ public double uvToX(double u, double v) {
+ return v;
+ }
+
+ @Override
+ public double uvToY(double u, double v) {
+ return u;
+ }
+
+ @Override
+ public double uvToZ(double u, double v) {
+ return -1;
+ }
+ };
+ return xyzTransforms;
+ }
+
+ /**
+ * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
+ * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
+ * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
+ * coordinate equal to (0, 0).
+ */
+ private interface UvTransform {
+
+ /**
+ * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
+ * (which may lie outside the range [-1, 1]).
+ */
+ double xyzToU(double x, double y, double z);
+
+ /**
+ * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
+ * (which may lie outside the range [-1, 1]).
+ */
+ double xyzToV(double x, double y, double z);
+ }
+
+ /**
+ * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
+ * resulting vectors are not necessarily of unit length.
+ */
+ private interface XyzTransform {
+
+ /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
+ double uvToX(double u, double v);
+
+ /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
+ double uvToY(double u, double v);
+
+ /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
+ double uvToZ(double u, double v);
+ }
+}
diff --git a/media/java/android/media/AudioHalVersionInfo.aidl b/media/java/android/media/AudioHalVersionInfo.aidl
new file mode 100644
index 0000000..a83f8c8
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.aidl
@@ -0,0 +1,18 @@
+/* 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.media;
+
+parcelable AudioHalVersionInfo;
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
new file mode 100644
index 0000000..985a758
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -0,0 +1,174 @@
+/*
+ * 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Defines the audio HAL version.
+ *
+ * @hide
+ */
+@TestApi
+public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHalVersionInfo> {
+ /**
+ * Indicate the audio HAL is implemented with HIDL (HAL interface definition language).
+ *
+ * @see <a href="https://source.android.com/docs/core/architecture/hidl/">HIDL</a>
+ * <p>The value of AUDIO_HAL_TYPE_HIDL should match the value of {@link
+ * android.media.AudioHalVersion.Type#HIDL}.
+ */
+ public static final int AUDIO_HAL_TYPE_HIDL = 0;
+
+ /**
+ * Indicate the audio HAL is implemented with AIDL (Android Interface Definition Language).
+ *
+ * @see <a href="https://source.android.com/docs/core/architecture/aidl/">AIDL</a>
+ * <p>The value of AUDIO_HAL_TYPE_AIDL should match the value of {@link
+ * android.media.AudioHalVersion.Type#AIDL}.
+ */
+ public static final int AUDIO_HAL_TYPE_AIDL = 1;
+
+ /** @hide */
+ @IntDef(
+ flag = false,
+ prefix = "AUDIO_HAL_TYPE_",
+ value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+ public @interface AudioHalType {}
+
+ /** AudioHalVersionInfo object of all valid Audio HAL versions. */
+ public static final @NonNull AudioHalVersionInfo AIDL_1_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_AIDL, 1 /* major */, 0 /* minor */);
+
+ public static final @NonNull AudioHalVersionInfo HIDL_7_1 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 1 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_7_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_6_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 6 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_5_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 5 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_4_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 4 /* major */, 0 /* minor */);
+ public static final @NonNull AudioHalVersionInfo HIDL_2_0 =
+ new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 2 /* major */, 0 /* minor */);
+
+ /**
+ * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
+ * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp.
+ */
+ // TODO: add AIDL_1_0 with sAudioHALVersions.
+ public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
+ List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
+
+ private static final String TAG = "AudioHalVersionInfo";
+ private AudioHalVersion mHalVersion = new AudioHalVersion();
+
+ public @AudioHalType int getHalType() {
+ return mHalVersion.type;
+ }
+
+ public int getMajorVersion() {
+ return mHalVersion.major;
+ }
+
+ public int getMinorVersion() {
+ return mHalVersion.minor;
+ }
+
+ /** String representative of AudioHalVersion.Type */
+ private static @NonNull String typeToString(@AudioHalType int type) {
+ if (type == AudioHalVersion.Type.HIDL) {
+ return "HIDL";
+ } else if (type == AudioHalVersion.Type.AIDL) {
+ return "AIDL";
+ } else {
+ return "INVALID";
+ }
+ }
+
+ /** String representative of type, major and minor */
+ private static @NonNull String toString(@AudioHalType int type, int major, int minor) {
+ return typeToString(type) + ":" + Integer.toString(major) + "." + Integer.toString(minor);
+ }
+
+ private AudioHalVersionInfo(@AudioHalType int type, int major, int minor) {
+ mHalVersion.type = type;
+ mHalVersion.major = major;
+ mHalVersion.minor = minor;
+ }
+
+ private AudioHalVersionInfo(Parcel in) {
+ mHalVersion = in.readTypedObject(AudioHalVersion.CREATOR);
+ }
+
+ /** String representative of this (AudioHalVersionInfo) object */
+ @Override
+ public String toString() {
+ return toString(mHalVersion.type, mHalVersion.major, mHalVersion.minor);
+ }
+
+ /**
+ * Compare two HAL versions by comparing their index in VERSIONS.
+ *
+ * <p>Normally all AudioHalVersionInfo object to compare should exist in the VERSIONS list. If
+ * both candidates exist in the VERSIONS list, smaller index means newer. Any candidate not
+ * exist in the VERSIONS list will be considered to be oldest version.
+ *
+ * @return 0 if the HAL version is the same as the other HAL version. Positive if the HAL
+ * version is newer than the other HAL version. Negative if the HAL version is older than
+ * the other version.
+ */
+ @Override
+ public int compareTo(@NonNull AudioHalVersionInfo other) {
+ int indexOther = VERSIONS.indexOf(other);
+ int indexThis = VERSIONS.indexOf(this);
+ if (indexThis < 0 || indexOther < 0) {
+ return indexThis - indexOther;
+ }
+ return indexOther - indexThis;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull android.os.Parcel out, int flag) {
+ out.writeTypedObject(mHalVersion, flag);
+ }
+
+ public static final @NonNull Parcelable.Creator<AudioHalVersionInfo> CREATOR =
+ new Parcelable.Creator<AudioHalVersionInfo>() {
+ @Override
+ public AudioHalVersionInfo createFromParcel(@NonNull Parcel in) {
+ return new AudioHalVersionInfo(in);
+ }
+
+ @Override
+ public AudioHalVersionInfo[] newArray(int size) {
+ return new AudioHalVersionInfo[size];
+ }
+ };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9c5313a..ae0d45f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8502,13 +8502,14 @@
}
/**
- * Returns the audio HAL version in the form MAJOR.MINOR. If there is no audio HAL found, null
- * will be returned.
+ * Returns an {@link AudioHalVersionInfo} indicating the Audio Hal Version. If there is no audio
+ * HAL found, null will be returned.
*
+ * @return @see @link #AudioHalVersionInfo The version of Audio HAL.
* @hide
*/
@TestApi
- public static @Nullable String getHalVersion() {
+ public static @Nullable AudioHalVersionInfo getHalVersion() {
try {
return getService().getHalVersion();
} catch (RemoteException e) {
diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java
index 47358be..05f3c5a 100644
--- a/media/java/android/media/AudioPresentation.java
+++ b/media/java/android/media/AudioPresentation.java
@@ -54,7 +54,11 @@
private final int mProgramId;
private final ULocale mLanguage;
- /** @hide */
+ /**
+ * The ContentClassifier int definitions represent the AudioPresentation content
+ * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
+ * @hide
+ */
@IntDef(
value = {
CONTENT_UNKNOWN,
@@ -67,11 +71,6 @@
CONTENT_EMERGENCY,
CONTENT_VOICEOVER,
})
-
- /**
- * The ContentClassifier int definitions represent the AudioPresentation content
- * classifier (as per TS 103 190-1 v1.2.1 4.3.3.8.1)
- */
@Retention(RetentionPolicy.SOURCE)
public @interface ContentClassifier {}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
old mode 100755
new mode 100644
index 2e766d5..ee453a4
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -22,6 +22,7 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioFormat;
import android.media.AudioFocusInfo;
+import android.media.AudioHalVersionInfo;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioRecordingConfiguration;
import android.media.AudioRoutesInfo;
@@ -571,5 +572,5 @@
in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
boolean handlesvolumeAdjustment);
- String getHalVersion();
+ AudioHalVersionInfo getHalVersion();
}
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index bf30c50..30d90a8 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -4106,6 +4106,22 @@
public static final int AV1Level72 = 0x400000;
public static final int AV1Level73 = 0x800000;
+ /** DTS codec profile for DTS HRA. */
+ @SuppressLint("AllUpper")
+ public static final int DTS_HDProfileHRA = 0x1;
+ /** DTS codec profile for DTS Express. */
+ @SuppressLint("AllUpper")
+ public static final int DTS_HDProfileLBR = 0x2;
+ /** DTS codec profile for DTS-HD Master Audio */
+ @SuppressLint("AllUpper")
+ public static final int DTS_HDProfileMA = 0x4;
+ /** DTS codec profile for DTS:X Profile 1 */
+ @SuppressLint("AllUpper")
+ public static final int DTS_UHDProfileP1 = 0x1;
+ /** DTS codec profile for DTS:X Profile 2 */
+ @SuppressLint("AllUpper")
+ public static final int DTS_UHDProfileP2 = 0x2;
+
/**
* The profile of the media content. Depending on the type of media this can be
* one of the profile values defined in this class.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index f1b1d79..254eb44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -95,6 +95,7 @@
"libpowermanager",
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
+ "android.hardware.power-V4-ndk",
"libnativedisplay",
],
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 40eb507..9e97bd3 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "perf_hint"
+#include <aidl/android/hardware/power/SessionHint.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
@@ -25,14 +26,21 @@
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
+#include <chrono>
#include <utility>
#include <vector>
using namespace android;
using namespace android::os;
+using namespace std::chrono_literals;
+
+using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+
struct APerformanceHintSession;
+constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
+
struct APerformanceHintManager {
public:
static APerformanceHintManager* getInstance();
@@ -75,6 +83,8 @@
int64_t mFirstTargetMetTimestamp;
// Last target hit timestamp
int64_t mLastTargetMetTimestamp;
+ // Last hint reported from sendHint indexed by hint value
+ std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
std::vector<int64_t> mActualDurationsNanos;
std::vector<int64_t> mTimestampsNanos;
@@ -147,7 +157,12 @@
mPreferredRateNanos(preferredRateNanos),
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
- mLastTargetMetTimestamp(0) {}
+ mLastTargetMetTimestamp(0) {
+ const std::vector<AidlSessionHint> sessionHintRange{ndk::enum_range<AidlSessionHint>().begin(),
+ ndk::enum_range<AidlSessionHint>().end()};
+
+ mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+}
APerformanceHintSession::~APerformanceHintSession() {
binder::Status ret = mHintSession->close();
@@ -224,10 +239,16 @@
}
int APerformanceHintSession::sendHint(int32_t hint) {
- if (hint < 0) {
- ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+ if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
+ ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
return EINVAL;
}
+ int64_t now = elapsedRealtimeNano();
+
+ // Limit sendHint to a pre-detemined rate for safety
+ if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
+ return 0;
+ }
binder::Status ret = mHintSession->sendHint(hint);
@@ -235,6 +256,7 @@
ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
return EPIPE;
}
+ mLastHintSentTimestamp[hint] = now;
return 0;
}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 1881e60..0c2d3b6 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -122,9 +122,16 @@
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));
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+ usleep(110000); // Sleep for longer than the update timeout.
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+ // Expect to get rate limited if we try to send faster than the limiter allows
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(0));
result = APerformanceHint_sendHint(session, hintId);
EXPECT_EQ(0, result);
diff --git a/packages/CarrierDefaultApp/res/values-af/strings.xml b/packages/CarrierDefaultApp/res/values-af/strings.xml
index 51cb6c8..3bc18ce 100644
--- a/packages/CarrierDefaultApp/res/values-af/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-af/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Gaan in elk geval deur blaaier voort"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-am/strings.xml b/packages/CarrierDefaultApp/res/values-am/strings.xml
index d5d50ac..0efdbc4 100644
--- a/packages/CarrierDefaultApp/res/values-am/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-am/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ለመቀላቀል እየሞከሩ ያሉት አውታረ መረብ የደህንነት ችግሮች አሉበት።"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ለምሳሌ፣ የመግቢያ ገጹ የሚታየው ድርጅት ላይሆን ይችላል።"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ለማንኛውም በአሳሽ በኩል ይቀጥሉ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ar/strings.xml b/packages/CarrierDefaultApp/res/values-ar/strings.xml
index 0c67de7..cd979b2 100644
--- a/packages/CarrierDefaultApp/res/values-ar/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ar/strings.xml
@@ -16,4 +16,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المؤسسة المعروضة."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"المتابعة على أي حال عبر المتصفح"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-as/strings.xml b/packages/CarrierDefaultApp/res/values-as/strings.xml
index 4b36b06..fdafe2b 100644
--- a/packages/CarrierDefaultApp/res/values-as/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-as/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"আপুনি সংযোগ কৰিবলৈ বিচৰা নেটৱৰ্কটোত সুৰক্ষাজনিত সমস্যা আছে।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"উদাহৰণস্বৰূপে, আপোনাক দেখুওৱা লগ ইনৰ পৃষ্ঠাটো প্ৰতিষ্ঠানটোৰ নিজা নহ\'বও পাৰে।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"তথাপিও ব্ৰাউজাৰৰ জৰিয়তে অব্যাহত ৰাখক"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-az/strings.xml b/packages/CarrierDefaultApp/res/values-az/strings.xml
index d1af3c9..32c3c1c 100644
--- a/packages/CarrierDefaultApp/res/values-az/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-az/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Qoşulmaq istədiyiniz şəbəkənin təhlükəsizlik problemləri var."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Məsələn, giriş səhifəsi göstərilən təşkilata aid olmaya bilər."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Hər bir halda brazuer ilə davam edin"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
index 5d55790..932fc03 100644
--- a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate da se pridružite ima bezbednosnih problema."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Na primer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko pregledača"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-be/strings.xml b/packages/CarrierDefaultApp/res/values-be/strings.xml
index 3ad85f2..20606f6 100644
--- a/packages/CarrierDefaultApp/res/values-be/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-be/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"У сеткі, да якой вы спрабуеце далучыцца, ёсць праблемы з бяспекай."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Напрыклад, старонка ўваходу можа не належаць указанай арганізацыі."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Усё роўна працягнуць праз браўзер"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bg/strings.xml b/packages/CarrierDefaultApp/res/values-bg/strings.xml
index f5308f0..46a9db5 100644
--- a/packages/CarrierDefaultApp/res/values-bg/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bg/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Например страницата за вход може да не принадлежи на показаната организация."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Продължаване през браузър въпреки това"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bn/strings.xml b/packages/CarrierDefaultApp/res/values-bn/strings.xml
index 448c42b3..0826ae1 100644
--- a/packages/CarrierDefaultApp/res/values-bn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bn/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন সেটিতে নিরাপত্তাজনিত সমস্যা আছে।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"যেমন, লগ-ইন পৃষ্ঠাটি যে প্রতিষ্ঠানের পৃষ্ঠা বলে দেখানো আছে, আসলে তা নাও হতে পারে৷"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"যাই হোক, ব্রাউজারের মাধ্যমে চালিয়ে যান"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-bs/strings.xml b/packages/CarrierDefaultApp/res/values-bs/strings.xml
index bc1ff33..e2bc342 100644
--- a/packages/CarrierDefaultApp/res/values-bs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bs/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate pristupiti ima sigurnosnih problema."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Naprimjer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko preglednika"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ca/strings.xml b/packages/CarrierDefaultApp/res/values-ca/strings.xml
index 66a8f37..bdde567 100644
--- a/packages/CarrierDefaultApp/res/values-ca/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ca/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"La xarxa a què et vols connectar té problemes de seguretat."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continua igualment mitjançant el navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-cs/strings.xml b/packages/CarrierDefaultApp/res/values-cs/strings.xml
index 5431836..d5fdac9 100644
--- a/packages/CarrierDefaultApp/res/values-cs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-cs/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Přihlašovací stránka například nemusí patřit zobrazované organizaci."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Přesto pokračovat prostřednictvím prohlížeče"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-da/strings.xml b/packages/CarrierDefaultApp/res/values-da/strings.xml
index b212117..8b2bb7c 100644
--- a/packages/CarrierDefaultApp/res/values-da/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-da/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Fortsæt alligevel via browseren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-de/strings.xml b/packages/CarrierDefaultApp/res/values-de/strings.xml
index 95639ad..21af41c 100644
--- a/packages/CarrierDefaultApp/res/values-de/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-de/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Beispiel: Die Log-in-Seite gehört eventuell nicht zur angezeigten Organisation."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Trotzdem in einem Browser fortfahren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-el/strings.xml b/packages/CarrierDefaultApp/res/values-el/strings.xml
index 016e68f..7514314 100644
--- a/packages/CarrierDefaultApp/res/values-el/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-el/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml b/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
index a925a30..11d9437 100644
--- a/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rAU/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
index a925a30..b5e53ae 100644
--- a/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rCA/strings.xml
@@ -2,7 +2,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="5247871339820894594">"CarrierDefaultApp"</string>
- <string name="android_system_label" msgid="2797790869522345065">"Mobile Operator"</string>
+ <string name="android_system_label" msgid="2797790869522345065">"Mobile Carrier"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Mobile data has run out"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Your mobile data has been deactivated"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"Tap to visit the %s website"</string>
@@ -11,7 +11,13 @@
<string name="no_mobile_data_connection" msgid="544980465184147010">"Add data or roaming plan through %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Mobile data status"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Sign in to mobile network"</string>
- <string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
- <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
+ <string name="ssl_error_warning" msgid="3127935140338254180">"The network you’re trying to join has security issues."</string>
+ <string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page may not belong to the organization shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml b/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
index a925a30..11d9437 100644
--- a/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rGB/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml b/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
index a925a30..11d9437 100644
--- a/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rIN/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network that you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page might not belong to the organisation shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml b/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
index d7ae1ce..f8a50e4 100644
--- a/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-en-rXC/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"The network you’re trying to join has security issues."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"For example, the login page may not belong to the organization shown."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continue anyway via browser"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommends a data boost"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Buy a network boost for better performance"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Not now"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Manage"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Purchase a network boost."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
index 0455603..bddae48 100644
--- a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos desde el navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-es/strings.xml b/packages/CarrierDefaultApp/res/values-es/strings.xml
index b5d038c..dd56770 100644
--- a/packages/CarrierDefaultApp/res/values-es/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es/strings.xml
@@ -8,10 +8,22 @@
<string name="portal_notification_detail" msgid="2295729385924660881">"Toca para acceder al sitio web de %s"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Ponte en contacto con tu proveedor de servicios (%s)"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Sin conexión de datos móviles"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Añade un plan de datos o de itinerancia a través de %s"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Añade un plan de datos o de roaming a través de %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Estado de la conexión de datos móviles"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Iniciar sesión en una red móvil"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas unirte tiene problemas de seguridad."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos a través del navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-et/strings.xml b/packages/CarrierDefaultApp/res/values-et/strings.xml
index 28cc9a9..8cdc291 100644
--- a/packages/CarrierDefaultApp/res/values-et/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-et/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Jätka siiski brauseris"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-eu/strings.xml b/packages/CarrierDefaultApp/res/values-eu/strings.xml
index 04e641f..22565dc 100644
--- a/packages/CarrierDefaultApp/res/values-eu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-eu/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Erabili nahi duzun sareak segurtasun-arazoak ditu."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Adibidez, baliteke saioa hasteko orria adierazitako erakundearena ez izatea."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Jarraitu arakatzailearen bidez, halere"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fa/strings.xml b/packages/CarrierDefaultApp/res/values-fa/strings.xml
index 5328a03..ecb9930 100644
--- a/packages/CarrierDefaultApp/res/values-fa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fa/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"شبکهای که میخواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"درهر صورت ازطریق مرورگر ادامه یابد"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fi/strings.xml b/packages/CarrierDefaultApp/res/values-fi/strings.xml
index d416c1d..850f9db 100644
--- a/packages/CarrierDefaultApp/res/values-fi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fi/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Verkossa, johon yrität muodostaa yhteyttä, havaittiin turvallisuusongelmia."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Jatka selaimen kautta"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
index 1b8c262..61fc2e4 100644
--- a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau que vous essayez de joindre présente des problèmes de sécurité."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion pourrait ne pas appartenir à l\'organisation représentée."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans un navigateur"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr/strings.xml b/packages/CarrierDefaultApp/res/values-fr/strings.xml
index 3ae9570..ef1857d 100644
--- a/packages/CarrierDefaultApp/res/values-fr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau auquel vous essayez de vous connecter présente des problèmes de sécurité."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans le navigateur"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-gl/strings.xml b/packages/CarrierDefaultApp/res/values-gl/strings.xml
index 4f199ca..6dde8a8 100644
--- a/packages/CarrierDefaultApp/res/values-gl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede á que tentas unirte ten problemas de seguranza."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, é posible que a páxina de inicio de sesión non pertenza á organización que se mostra."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar igualmente co navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-gu/strings.xml b/packages/CarrierDefaultApp/res/values-gu/strings.xml
index 57710d0..473a035 100644
--- a/packages/CarrierDefaultApp/res/values-gu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gu/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"તમે જોડાવાનો પ્રયાસ કરી રહ્યા છો તે નેટવર્કમાં સુરક્ષા સંબંધી સમસ્યાઓ છે."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ઉદાહરણ તરીકે, લોગિન પૃષ્ઠ બતાવવામાં આવેલી સંસ્થાનું ન પણ હોય."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"તો પણ બ્રાઉઝર મારફતે ચાલુ રાખો"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hi/strings.xml b/packages/CarrierDefaultApp/res/values-hi/strings.xml
index b9d6f42..d878c1c 100644
--- a/packages/CarrierDefaultApp/res/values-hi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hi/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"आप जिस नेटवर्क में शामिल होने की कोशिश कर रहे हैं उसमें सुरक्षा से जुड़ी समस्याएं हैं."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"उदाहरण के लिए, हो सकता है कि लॉगिन पेज दिखाए गए संगठन का ना हो."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ब्राउज़र के ज़रिए किसी भी तरह जारी रखें"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hr/strings.xml b/packages/CarrierDefaultApp/res/values-hr/strings.xml
index 66531a7..0da2280 100644
--- a/packages/CarrierDefaultApp/res/values-hr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi putem preglednika"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hu/strings.xml b/packages/CarrierDefaultApp/res/values-hu/strings.xml
index 4ae6ea6..95c1db8 100644
--- a/packages/CarrierDefaultApp/res/values-hu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hu/strings.xml
@@ -8,10 +8,22 @@
<string name="portal_notification_detail" msgid="2295729385924660881">"Koppintson a(z) %s webhely meglátogatásához"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Vegye fel a kapcsolatot szolgáltatójával (%s)"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nincs mobiladat-kapcsolat"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Adjon hozzá előfizetést vagy barangolási csomagot a következőn keresztül: %s"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Adjon hozzá előfizetést vagy roamingcsomagot a következőn keresztül: %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Mobiladat-állapot"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Bejelentkezés a mobilhálózatra"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Például lehetséges, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Folytatás ennek ellenére böngészőn keresztül"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-hy/strings.xml b/packages/CarrierDefaultApp/res/values-hy/strings.xml
index 99398bc..a846e04 100644
--- a/packages/CarrierDefaultApp/res/values-hy/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hy/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Ցանցը, որին փորձում եք միանալ, անվտանգության խնդիրներ ունի:"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Օրինակ՝ մուտքի էջը կարող է ցուցադրված կազմակերպության էջը չլինել:"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Շարունակել դիտարկիչի միջոցով"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-in/strings.xml b/packages/CarrierDefaultApp/res/values-in/strings.xml
index f48d31f..488ad09 100644
--- a/packages/CarrierDefaultApp/res/values-in/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-in/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Jaringan yang ingin Anda masuki memiliki masalah keamanan."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Misalnya, halaman login mungkin bukan milik organisasi yang ditampilkan."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Tetap lanjutkan melalui browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-is/strings.xml b/packages/CarrierDefaultApp/res/values-is/strings.xml
index cdba5be..ab4981d 100644
--- a/packages/CarrierDefaultApp/res/values-is/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-is/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Öryggisvandamál eru á netinu sem þú ert að reyna að tengjast."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Til dæmis getur verið að innskráningarsíðan tilheyri ekki fyrirtækinu sem birtist."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Halda samt áfram í vafra"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-it/strings.xml b/packages/CarrierDefaultApp/res/values-it/strings.xml
index a62ae86..95ea060 100644
--- a/packages/CarrierDefaultApp/res/values-it/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-it/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continua comunque dal browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-iw/strings.xml b/packages/CarrierDefaultApp/res/values-iw/strings.xml
index 550936c..263ed1a 100644
--- a/packages/CarrierDefaultApp/res/values-iw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-iw/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"המשך בכל זאת באמצעות דפדפן"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ja/strings.xml b/packages/CarrierDefaultApp/res/values-ja/strings.xml
index e5977ae..3b22ae1 100644
--- a/packages/CarrierDefaultApp/res/values-ja/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ja/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ブラウザから続行"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ka/strings.xml b/packages/CarrierDefaultApp/res/values-ka/strings.xml
index 91ae46d..4b9cd38 100644
--- a/packages/CarrierDefaultApp/res/values-ka/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ka/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ქსელს, რომელთან დაკავშრებასაც ცდილობთ, უსაფრთხოების პრობლემები აქვს."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"მაგალითად, სისტემაში შესვლის გვერდი შეიძლება არ ეკუთვნოდეს ნაჩვენებ ორგანიზაციას."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"მაინც ბრაუზერში გაგრძელება"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-kk/strings.xml b/packages/CarrierDefaultApp/res/values-kk/strings.xml
index 0fb57bc..fce8dd2 100644
--- a/packages/CarrierDefaultApp/res/values-kk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kk/strings.xml
@@ -3,15 +3,27 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
<string name="android_system_label" msgid="2797790869522345065">"Мобильдік байланыс операторы"</string>
- <string name="portal_notification_id" msgid="5155057562457079297">"Мобильдік деректер бітті"</string>
- <string name="no_data_notification_id" msgid="668400731803969521">"Мобильдік деректер өшірілді"</string>
+ <string name="portal_notification_id" msgid="5155057562457079297">"Мобильдік интернет бітті"</string>
+ <string name="no_data_notification_id" msgid="668400731803969521">"Мобильдік интернет өшірілді"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s вебсайтына кіру үшін түртіңіз"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Қызмет көрсетушіге (%s) хабарласыңыз"</string>
- <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобильдік деректер байланысы жоқ"</string>
+ <string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобильдік интернет байланысы жоқ"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"%s арқылы деректер не роуминг жоспарын енгізу"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобильді деректер күйі"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Мобильдік желіге тіркелу"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Қосылайын деп жатқан желіңізде қауіпсіздік мәселелері бар."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Мысалы, кіру беті көрсетілген ұйымға тиесілі болмауы мүмкін."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Бәрібір браузер арқылы жалғастыру"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-km/strings.xml b/packages/CarrierDefaultApp/res/values-km/strings.xml
index 6ef1066..983f09e 100644
--- a/packages/CarrierDefaultApp/res/values-km/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-km/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"បណ្តាញដែលអ្នកកំពុងព្យាយាមចូលមានបញ្ហាសុវត្ថិភាព។"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ឧទាហរណ៍៖ ទំព័រចូលនេះអាចនឹងមិនមែនជាកម្មសិទ្ធិរបស់ស្ថាប័នដែលបានបង្ហាញនេះទេ។"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"យ៉ាងណាក៏ដោយនៅតែបន្តតាមរយៈកម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-kn/strings.xml b/packages/CarrierDefaultApp/res/values-kn/strings.xml
index ea4b09a..86b29ce 100644
--- a/packages/CarrierDefaultApp/res/values-kn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kn/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ನೀವು ಸೇರಬೇಕೆಂದಿರುವ ನೆಟ್ವರ್ಕ್, ಭದ್ರತೆ ಸಮಸ್ಯೆಗಳನ್ನು ಹೊಂದಿದೆ."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ಉದಾಹರಣೆಗೆ, ಲಾಗಿನ್ ಪುಟವು ತೋರಿಸಲಾಗಿರುವ ಸಂಸ್ಥೆಗೆ ಸಂಬಂಧಿಸಿಲ್ಲದಿರಬಹುದು."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ಪರವಾಗಿಲ್ಲ, ಬ್ರೌಸರ್ ಮೂಲಕ ಮುಂದುವರಿಸಿ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ko/strings.xml b/packages/CarrierDefaultApp/res/values-ko/strings.xml
index d6b3d61..3c6f4a6 100644
--- a/packages/CarrierDefaultApp/res/values-ko/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ko/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"브라우저를 통해 계속하기"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ky/strings.xml b/packages/CarrierDefaultApp/res/values-ky/strings.xml
index 199476f..3cece7d 100644
--- a/packages/CarrierDefaultApp/res/values-ky/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ky/strings.xml
@@ -1,17 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="5247871339820894594">"ОператордунДемейкиКолдонмосу"</string>
+ <string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
<string name="android_system_label" msgid="2797790869522345065">"Мобилдик байланыш оператору"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Мобилдик Интернетиңиздин трафиги түгөндү"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Мобилдик Интернет өчүрүлгөн"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s сайтына баш багуу үчүн басыңыз"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"%s Интернет провайдери менен байланышыңыз"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобилдик Интернет жок"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"%s аркылуу дайындарды же роуминг планын кошуу"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"%s аркылуу маалыматтарды же роуминг планын кошуу"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Мобилдик Интернеттин абалы"</string>
<string name="action_bar_label" msgid="4290345990334377177">"Мобилдик тармакка кирүү"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Кошулайын деген тармагыңызда коопсуздук көйгөйлөрү бар."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Мисалы, аккаунтка кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Баары бир серепчи аркылуу улантуу"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lo/strings.xml b/packages/CarrierDefaultApp/res/values-lo/strings.xml
index 4a21d7c..c6c0533 100644
--- a/packages/CarrierDefaultApp/res/values-lo/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lo/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ເຄືອຂ່າຍທີ່ທ່ານກຳລັງເຂົ້າຮ່ວມມີບັນຫາຄວາມປອດໄພ."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ຕົວຢ່າງ, ໜ້າເຂົ້າສູ່ລະບົບອາດຈະບໍ່ແມ່ນຂອງອົງກອນທີ່ປາກົດ."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ດຳເນີນການຕໍ່ຜ່ານໂປຣແກຣມທ່ອງເວັບ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lt/strings.xml b/packages/CarrierDefaultApp/res/values-lt/strings.xml
index be452b7..fe33b1d 100644
--- a/packages/CarrierDefaultApp/res/values-lt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lt/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Kilo tinklo, prie kurio bandote prisijungti, saugos problemų."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vis tiek tęsti naudojant naršyklę"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-lv/strings.xml b/packages/CarrierDefaultApp/res/values-lv/strings.xml
index 80a9b58..f8864e2 100644
--- a/packages/CarrierDefaultApp/res/values-lv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lv/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Tīklā, kuram mēģināt pievienoties, ir drošības problēmas."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Tomēr turpināt, izmantojot pārlūkprogrammu"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mk/strings.xml b/packages/CarrierDefaultApp/res/values-mk/strings.xml
index 96b222c..0b8daaf 100644
--- a/packages/CarrierDefaultApp/res/values-mk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mk/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата на која се обидувате да се придружите има проблеми со безбедноста."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"На пример, страницата за најавување може да не припаѓа на прикажаната организација."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Сепак продолжи преку прелистувач"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ml/strings.xml b/packages/CarrierDefaultApp/res/values-ml/strings.xml
index ae08ade..f27d4d8 100644
--- a/packages/CarrierDefaultApp/res/values-ml/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ml/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"നിങ്ങൾ ചേരാൻ ശ്രമിക്കുന്ന നെറ്റ്വർക്കിൽ സുരക്ഷാ പ്രശ്നങ്ങളുണ്ടായിരിക്കാം."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ഉദാഹരണത്തിന്, കാണിച്ചിരിക്കുന്ന ഓർഗനൈസേഷന്റേതായിരിക്കില്ല ലോഗിൻ പേജ്."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"എന്തായാലും ബ്രൗസർ വഴി തുടരുക"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mn/strings.xml b/packages/CarrierDefaultApp/res/values-mn/strings.xml
index 1a9b72e..354bd34 100644
--- a/packages/CarrierDefaultApp/res/values-mn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mn/strings.xml
@@ -5,7 +5,7 @@
<string name="android_system_label" msgid="2797790869522345065">"Мобайл оператор компани"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Мобайл дата дууссан"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Таны мобайл датаг идэвхгүй болгосон"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"%s вэб хуудсанд зочлохын тулд товших"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"%s веб хуудсанд зочлохын тулд товших"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"%s үйлчилгээ үзүүлэгчтэйгээ холбогдоно уу"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Мобайл дата холболт алга"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"Дата эсвэл роуминг төлөвлөгөөг %s-р нэмнэ үү"</string>
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Таны холбогдох гэж буй сүлжээ аюулгүй байдлын асуудалтай байна."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Жишээлбэл нэвтрэх хуудас нь харагдаж буй байгууллагынх биш байж болно."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ямар ч тохиолдолд хөтчөөр үргэлжлүүлэх"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-mr/strings.xml b/packages/CarrierDefaultApp/res/values-mr/strings.xml
index 79cc4aa..c61d3c8 100644
--- a/packages/CarrierDefaultApp/res/values-mr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"तुम्ही ज्या नेटवर्कमध्ये सामील होण्याचा प्रयत्न करत आहात त्यात सुरक्षितता समस्या आहेत."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणार्थ, लॉग इन पृष्ठ दर्शवलेल्या संस्थेच्या मालकीचे नसू शकते."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"तरीही ब्राउझरद्वारे सुरू ठेवा"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ms/strings.xml b/packages/CarrierDefaultApp/res/values-ms/strings.xml
index 7aca5f0..366463f 100644
--- a/packages/CarrierDefaultApp/res/values-ms/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ms/strings.xml
@@ -5,7 +5,7 @@
<string name="android_system_label" msgid="2797790869522345065">"Pembawa Mudah Alih"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Data mudah alih telah habis"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Data mudah alih anda telah dinyahaktifkan"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"Ketik untuk melawat tapak web %s"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"Ketik untuk melawat laman web %s"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"Sila hubungi penyedia perkhidmatan anda, %s"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Tiada sambungan data mudah alih"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"Tambahkan data atau pelan perayauan melalui %s"</string>
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Rangkaian yang cuba anda sertai mempunyai isu keselamatan."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Teruskan juga melalui penyemak imbas"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-my/strings.xml b/packages/CarrierDefaultApp/res/values-my/strings.xml
index 82372f9..2fa6188 100644
--- a/packages/CarrierDefaultApp/res/values-my/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-my/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"သင်ချိတ်ဆက်ရန် ကြိုးစားနေသည့် ကွန်ရက်တွင် လုံခြုံရေးပြဿနာများ ရှိနေသည်။"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ဥပမာ− ဝင်ရောက်ရန် စာမျက်နှာသည် ပြသထားသည့် အဖွဲ့အစည်းနှင့် သက်ဆိုင်မှုမရှိခြင်း ဖြစ်နိုင်ပါသည်။"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"မည်သို့ပင်ဖြစ်စေ ဘရောက်ဇာမှတစ်ဆင့် ရှေ့ဆက်ရန်"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-nb/strings.xml b/packages/CarrierDefaultApp/res/values-nb/strings.xml
index 1bb9826..16f8eaa 100644
--- a/packages/CarrierDefaultApp/res/values-nb/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nb/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Det er for eksempel mulig at påloggingssiden ikke tilhører organisasjonen som vises."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Fortsett likevel via nettleseren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ne/strings.xml b/packages/CarrierDefaultApp/res/values-ne/strings.xml
index 2349f9d..cb175df 100644
--- a/packages/CarrierDefaultApp/res/values-ne/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ne/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"तपाईंले सामेल हुने प्रयास गरिरहनु भएको नेटवर्कमा सुरक्षा सम्बन्धी समस्याहरू छन्।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणका लागि, लग इन पृष्ठ देखाइएको संस्थाको नहुन सक्छ।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"जे भए पनि ब्राउजर मार्फत जारी राख्नुहोस्"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-nl/strings.xml b/packages/CarrierDefaultApp/res/values-nl/strings.xml
index 4e2c09b..8511ff5 100644
--- a/packages/CarrierDefaultApp/res/values-nl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Het netwerk waarmee je verbinding probeert te maken, heeft beveiligingsproblemen."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Toch doorgaan via browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-or/strings.xml b/packages/CarrierDefaultApp/res/values-or/strings.xml
index fd51ed0..65fd7bb 100644
--- a/packages/CarrierDefaultApp/res/values-or/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-or/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ଆପଣ ଯୋଗ ଦେବାକୁ ଚେଷ୍ଟା କରୁଥିବା ନେଟୱର୍କର ସୁରକ୍ଷା ସମସ୍ୟା ଅଛି।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ଉଦାହରଣସ୍ୱରୂପ, ଲଗଇନ୍ ପୃଷ୍ଠା ଦେଖାଯାଇଥିବା ସଂସ୍ଥାର ହୋଇନଥାଇପାରେ।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ବ୍ରାଉଜର୍ ଜରିଆରେ ଯେମିତିବି ହେଉ ଜାରି ରଖନ୍ତୁ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pa/strings.xml b/packages/CarrierDefaultApp/res/values-pa/strings.xml
index f4d4053..0f096ab 100644
--- a/packages/CarrierDefaultApp/res/values-pa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pa/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ਤੁਸੀਂ ਜਿਸ ਨੈੱਟਵਰਕ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੇ ਹੋ ਉਸ ਵਿੱਚ ਸੁਰੱਖਿਆ ਸਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ।"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ਉਦਾਹਰਣ ਵੱਜੋਂ, ਲੌਗ-ਇਨ ਪੰਨਾ ਦਿਖਾਈ ਗਈ ਸੰਸਥਾ ਨਾਲ ਸੰਬੰਧਿਤ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ਬ੍ਰਾਊਜ਼ਰ ਰਾਹੀਂ ਫਿਰ ਵੀ ਜਾਰੀ ਰੱਖੋ"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pl/strings.xml b/packages/CarrierDefaultApp/res/values-pl/strings.xml
index ac45e27..08bc767 100644
--- a/packages/CarrierDefaultApp/res/values-pl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Kontynuuj mimo to w przeglądarce"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
index 926de65..23b4152 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
index 107a9c2..34a564d 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rPT/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual está a tentar aceder tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de início de sessão pode não pertencer à entidade apresentada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim através do navegador"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Otimização de rede"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomenda um serviço de otimização de dados"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compre um serviço de otimização de rede para melhorar o desempenho"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Agora não"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gerir"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Comprar um serviço de otimização de rede."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt/strings.xml b/packages/CarrierDefaultApp/res/values-pt/strings.xml
index 926de65..23b4152 100644
--- a/packages/CarrierDefaultApp/res/values-pt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ro/strings.xml b/packages/CarrierDefaultApp/res/values-ro/strings.xml
index b91aa813..165952c 100644
--- a/packages/CarrierDefaultApp/res/values-ro/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ro/strings.xml
@@ -5,13 +5,25 @@
<string name="android_system_label" msgid="2797790869522345065">"Operator de telefonie mobilă"</string>
<string name="portal_notification_id" msgid="5155057562457079297">"Datele mobile au expirat"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"Datele mobile au fost dezactivate"</string>
- <string name="portal_notification_detail" msgid="2295729385924660881">"Atingeți pentru a accesa site-ul %s"</string>
- <string name="no_data_notification_detail" msgid="3112125343857014825">"Contactați furnizorul de servicii %s"</string>
+ <string name="portal_notification_detail" msgid="2295729385924660881">"Atinge pentru a accesa site-ul %s"</string>
+ <string name="no_data_notification_detail" msgid="3112125343857014825">"Contactează furnizorul de servicii %s"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"Nu există o conexiune de date mobile"</string>
- <string name="no_mobile_data_connection" msgid="544980465184147010">"Adăugați un plan de date sau de roaming prin %s"</string>
+ <string name="no_mobile_data_connection" msgid="544980465184147010">"Adaugă un plan de date sau de roaming prin %s"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"Starea datelor mobile"</string>
- <string name="action_bar_label" msgid="4290345990334377177">"Conectați-vă la rețeaua mobilă"</string>
+ <string name="action_bar_label" msgid="4290345990334377177">"Conectează-te la rețeaua mobilă"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
- <string name="ssl_error_continue" msgid="1138548463994095584">"Continuați oricum prin browser"</string>
+ <string name="ssl_error_continue" msgid="1138548463994095584">"Continuă oricum prin browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ru/strings.xml b/packages/CarrierDefaultApp/res/values-ru/strings.xml
index ff24f1f..77ce91f 100644
--- a/packages/CarrierDefaultApp/res/values-ru/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ru/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Например, страница входа в аккаунт может быть фиктивной."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Продолжить в браузере"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-si/strings.xml b/packages/CarrierDefaultApp/res/values-si/strings.xml
index 378a534..fe981ca 100644
--- a/packages/CarrierDefaultApp/res/values-si/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-si/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"ඔබ සම්බන්ධ වීමට උත්සහ කරන ජාලයේ ආරක්ෂක ගැටළු ඇත."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"උදාහරණයක් ලෙස, පුරනය වන පිටුව පෙන්වා ඇති සංවිධානයට අයිති නැති විය හැක."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"කෙසේ වුවත් බ්රවුසරය හරහා ඉදිරියට යන්න"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sk/strings.xml b/packages/CarrierDefaultApp/res/values-sk/strings.xml
index 9fe38da..1a2ef10 100644
--- a/packages/CarrierDefaultApp/res/values-sk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sk/strings.xml
@@ -14,4 +14,10 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Sieť, ku ktorej sa pokúšate pripojiť, má problémy so zabezpečením"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Napríklad prihlasovacia stránka nemusí patriť uvedenej organizácii."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Pokračovať pomocou prehliadača"</string>
+ <string name="network_boost_notification_channel" msgid="5430986172506159199">"Zrýchlenie siete"</string>
+ <string name="network_boost_notification_title" msgid="8226368121348880044">"%s odporúča zrýchlenie dátového pripojenia"</string>
+ <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kúpte si zrýchlenie siete zvyšujúce výkon"</string>
+ <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Teraz nie"</string>
+ <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Spravovať"</string>
+ <string name="slice_purchase_app_label" msgid="915654761797446390">"Kúpte si zrýchlenie siete."</string>
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sl/strings.xml b/packages/CarrierDefaultApp/res/values-sl/strings.xml
index bdbc155..1a0f74b9 100644
--- a/packages/CarrierDefaultApp/res/values-sl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vseeno nadaljuj v brskalniku"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sq/strings.xml b/packages/CarrierDefaultApp/res/values-sq/strings.xml
index d4899e0..f6e1935 100644
--- a/packages/CarrierDefaultApp/res/values-sq/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sq/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Rrjeti në të cilin po përpiqesh të bashkohesh ka probleme sigurie."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"për shembull, faqja e identifikimit mund të mos i përkasë organizatës së shfaqur."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vazhdo gjithsesi nëpërmjet shfletuesit"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sr/strings.xml b/packages/CarrierDefaultApp/res/values-sr/strings.xml
index 34c3bdc..e615ead 100644
--- a/packages/CarrierDefaultApp/res/values-sr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Ипак настави преко прегледача"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sv/strings.xml b/packages/CarrierDefaultApp/res/values-sv/strings.xml
index 4e76c8d..778663b 100644
--- a/packages/CarrierDefaultApp/res/values-sv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sv/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Fortsätt ändå via webbläsaren"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
index a52a733..4f0745c 100644
--- a/packages/CarrierDefaultApp/res/values-sw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Endelea hata hivyo kupitia kivinjari"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ta/strings.xml b/packages/CarrierDefaultApp/res/values-ta/strings.xml
index 1a786fa..a1d2928 100644
--- a/packages/CarrierDefaultApp/res/values-ta/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ta/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"நீங்கள் சேர முயலும் நெட்வொர்க்கில் பாதுகாப்புச் சிக்கல்கள் உள்ளன."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"எடுத்துக்காட்டாக, உள்நுழைவுப் பக்கமானது காட்டப்படும் அமைப்பிற்குச் சொந்தமானதாக இல்லாமல் இருக்கலாம்."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"பரவாயில்லை, உலாவி வழியாகத் தொடர்க"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-te/strings.xml b/packages/CarrierDefaultApp/res/values-te/strings.xml
index 8877c0c..7139903 100644
--- a/packages/CarrierDefaultApp/res/values-te/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-te/strings.xml
@@ -3,15 +3,27 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="5247871339820894594">"CarrierDefaultApp"</string>
<string name="android_system_label" msgid="2797790869522345065">"మొబైల్ క్యారియర్"</string>
- <string name="portal_notification_id" msgid="5155057562457079297">"మొబైల్ డేటాని పూర్తిగా ఉపయోగించారు"</string>
+ <string name="portal_notification_id" msgid="5155057562457079297">"మొబైల్ డేటాను పూర్తిగా ఉపయోగించారు"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"మీ మొబైల్ డేటా నిష్క్రియం చేయబడింది"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s వెబ్సైట్ని సందర్శించడం కోసం నొక్కండి"</string>
<string name="no_data_notification_detail" msgid="3112125343857014825">"దయచేసి మీ సేవా ప్రదాత %sని సంప్రదించండి"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"మొబైల్ డేటా కనెక్షన్ లేదు"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"%s ద్వారా డేటాను లేదా రోమింగ్ ప్లాన్ను జోడించండి"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"మొబైల్ డేటా స్థితి"</string>
- <string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్వర్క్కి సైన్ ఇన్ చేయి"</string>
+ <string name="action_bar_label" msgid="4290345990334377177">"మొబైల్ నెట్వర్క్కి సైన్ ఇన్ చేయండి"</string>
<string name="ssl_error_warning" msgid="3127935140338254180">"మీరు చేరడానికి ప్రయత్నిస్తున్న నెట్వర్క్ భద్రతా సమస్యలను కలిగి ఉంది."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ఉదాహరణకు, లాగిన్ పేజీ చూపిన సంస్థకు చెందినది కాకపోవచ్చు."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించు"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-th/strings.xml b/packages/CarrierDefaultApp/res/values-th/strings.xml
index 8d30cfd..5c63bb1 100644
--- a/packages/CarrierDefaultApp/res/values-th/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-th/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-tl/strings.xml b/packages/CarrierDefaultApp/res/values-tl/strings.xml
index 083ec9a..9e320c8 100644
--- a/packages/CarrierDefaultApp/res/values-tl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tl/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"May mga isyu sa seguridad ang network na sinusubukan mong salihan."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Halimbawa, maaaring hindi pag-aari ng ipinapakitang organisasyon ang page ng login."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-tr/strings.xml b/packages/CarrierDefaultApp/res/values-tr/strings.xml
index aa17431..63616cc 100644
--- a/packages/CarrierDefaultApp/res/values-tr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tr/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Yine de tarayıcıyla devam et"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-uk/strings.xml b/packages/CarrierDefaultApp/res/values-uk/strings.xml
index 8381e35..bd44327 100644
--- a/packages/CarrierDefaultApp/res/values-uk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uk/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Наприклад, сторінка входу може не належати вказаній організації."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Усе одно продовжити у веб-переглядачі"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-ur/strings.xml b/packages/CarrierDefaultApp/res/values-ur/strings.xml
index fc286b8..3294cf5 100644
--- a/packages/CarrierDefaultApp/res/values-ur/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ur/strings.xml
@@ -6,7 +6,7 @@
<string name="portal_notification_id" msgid="5155057562457079297">"موبائل ڈیٹا ختم ہو چکا ہے"</string>
<string name="no_data_notification_id" msgid="668400731803969521">"آپ کا موبائل ڈیٹا غیر فعال کر دیا گیا ہے"</string>
<string name="portal_notification_detail" msgid="2295729385924660881">"%s ویب سائٹ ملاحظہ کرنے کیلئے تھپتھپائیں"</string>
- <string name="no_data_notification_detail" msgid="3112125343857014825">"براہ کرم اپنے خدمت کے فراہم کنندہ %s سے رابطہ کریں"</string>
+ <string name="no_data_notification_detail" msgid="3112125343857014825">"براہ کرم اپنے سروس فراہم کنندہ %s سے رابطہ کریں"</string>
<string name="no_mobile_data_connection_title" msgid="7449525772416200578">"کوئی موبائل ڈیٹا کنکشن نہیں ہے"</string>
<string name="no_mobile_data_connection" msgid="544980465184147010">"%s کے ذریعے ڈیٹا یا رومنگ پلان شامل کریں"</string>
<string name="mobile_data_status_notification_channel_name" msgid="833999690121305708">"موبائل ڈیٹا کی صورت حال"</string>
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"آپ جس نیٹ ورک میں شامل ہونے کی کوشش کر رہے ہیں، اس میں سیکیورٹی کے مسائل ہیں۔"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"مثال کے طور پر ہو سکتا ہے کہ لاگ ان صفحہ دکھائی گئی تنظیم سے تعلق نہ رکھتا ہو۔"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"براؤزر کے ذریعے بہرحال جاری رکھیں"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-uz/strings.xml b/packages/CarrierDefaultApp/res/values-uz/strings.xml
index f2801c8..4eca545 100644
--- a/packages/CarrierDefaultApp/res/values-uz/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uz/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Siz ulanmoqchi bo‘lgan tarmoqda xavfsizlik bilan bog‘liq muammolar mavjud."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Masalan, tizimga kirish sahifasi ko‘rsatilgan tashkilotga tegishli bo‘lmasligi mumkin."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Brauzerda davom ettirish"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-vi/strings.xml b/packages/CarrierDefaultApp/res/values-vi/strings.xml
index 1047cd4..d8f15e8 100644
--- a/packages/CarrierDefaultApp/res/values-vi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-vi/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Ví dụ: trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Vẫn tiếp tục qua trình duyệt"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
index f84cedb..4ce19f5 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"您尝试加入的网络存在安全问题。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"例如,登录页面可能并不属于页面上显示的单位。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"仍然通过浏览器继续操作"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
index ad76306..f019beb 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"您正在嘗試加入的網絡有安全性問題。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"例如,登入頁面可能並不屬於所顯示的機構。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
index ccf95c1..32724b5 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"你嘗試加入的網路有安全性問題。"</string>
<string name="ssl_error_example" msgid="6188711843183058764">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CarrierDefaultApp/res/values-zu/strings.xml b/packages/CarrierDefaultApp/res/values-zu/strings.xml
index 4ef80c1..669822c 100644
--- a/packages/CarrierDefaultApp/res/values-zu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zu/strings.xml
@@ -14,4 +14,16 @@
<string name="ssl_error_warning" msgid="3127935140338254180">"Inethiwekhi ozama ukuyijoyina inezinkinga zokuvikela."</string>
<string name="ssl_error_example" msgid="6188711843183058764">"Isibonelo, ikhasi lokungena ngemvume kungenzeka lingelenhlangano ebonisiwe."</string>
<string name="ssl_error_continue" msgid="1138548463994095584">"Qhubeka noma kunjalo ngesiphequluli"</string>
+ <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
+ <skip />
+ <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
+ <skip />
+ <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
+ <skip />
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-am/strings.xml b/packages/CompanionDeviceManager/res/values-am/strings.xml
index 476a25e..99d2041 100644
--- a/packages/CompanionDeviceManager/res/values-am/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-am/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> የእርስዎን <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> መሣሪያ እንዲደርስ ይፍቀዱለት"</string>
<string name="profile_name_watch" msgid="576290739483672360">"ሰዓት"</string>
<string name="chooser_title" msgid="2262294130493605839">"በ<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> የሚተዳደር <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ይምረጡ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር መተግበሪያው ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ ዕውቂያዎች፣ የቀን መቁጠሪያ፣ የጥሪ ምዝገባ ማስታወሻዎች እና በአቅራቢያ ያሉ የመሣሪያዎች ፈቃዶች እንዲደርስ ይፈቀድለታል።"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"የእርስዎን <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ለማስተዳደር መተግበሪያው ያስፈልጋል። <xliff:g id="APP_NAME">%2$s</xliff:g> ከእነዚህ ፈቃዶች ጋር መስተጋብር እንዲፈጥር ይፈቀድለታል፦"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ይህን መረጃ ከስልክዎ እንዲደርስበት ይፍቀዱለት"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"መሣሪያ ተሻጋሪ አገልግሎቶች"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> በእርስዎ መሣሪያዎች መካከል መተግበሪያዎችን በዥረት ለመልቀቅ የእርስዎን <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ወክሎ ፈቃድ እየጠየቀ ነው"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>ይህ የማይክሮፎን፣ የካሜራ እና የአካባቢ መዳረሻ እና ሌሎች በ<strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p> ላይ ያሉ አደገኛ ፈቃዶችን ሊያካትት ይችላል።እነዚህን ፈቃዶች በማንኛውም ጊዜ በ<strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>ላይ ቅንብሮችዎ ውስጥ መቀየር ይችላሉ።"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"የመተግበሪያ አዶ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"የተጨማሪ መረጃ አዝራር"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ስልክ"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"ኤስኤምኤስ"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"ዕውቂያዎች"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ቀን መቁጠሪያ"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"በአቅራቢያ ያሉ መሣሪያዎች"</string>
<string name="permission_storage" msgid="6831099350839392343">"ፎቶዎች እና ሚዲያ"</string>
<string name="permission_notification" msgid="693762568127741203">"ማሳወቂያዎች"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"መተግበሪያዎች"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"የእርስዎን ስልክ ቁጥር እና የአውታረ መረብ መረጃ መድረስ ይችላል። ጥሪዎችን ለማድረግ እና VoIP፣ የድምፅ መልዕክት፣ የጥሪ ማዘዋወር እና የጥሪ ምዝገባ ማስታወሻዎችን ለማርትዕ ያስፈልጋል"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"የእኛን የዕውቂያ ዝርዝር ማንበብ፣ መፍጠር ወይም ማርትዕ እንዲሁም በመሣሪያዎ ላይ ጥቅም ላይ የዋሉትን ሁሉንም መለያዎች ዝርዝር መድረስ ይችላል"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"እንደ እውቂያዎች፣ መልዕክቶች እና ፎቶዎች ያሉ መረጃዎችን ጨምሮ ሁሉንም ማሳወቂያዎች ማንበብ ይችላል"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"የስልክዎን መተግበሪያዎች በዥረት ይልቀቁ"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index d9ba555..051a629 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"السماح لتطبيق <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> بالوصول إلى <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_watch" msgid="576290739483672360">"الساعة"</string>
<string name="chooser_title" msgid="2262294130493605839">"اختَر <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ليديرها تطبيق <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح لتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والتقويم وسجلّات المكالمات والأجهزة المجاورة."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"التطبيق مطلوب لإدارة \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". سيتم السماح للتطبيق \"<xliff:g id="APP_NAME">%2$s</xliff:g>\" بالتفاعل مع هذه الأذونات."</string>
<string name="title_app_streaming" msgid="2270331024626446950">"السماح لتطبيق <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> بالوصول إلى هذه المعلومات من هاتفك"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"الخدمات التي تعمل بين الأجهزة"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"يطلب تطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> الحصول على إذن نيابةً عن <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> لمشاركة التطبيقات بين أجهزتك."</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>قد تتضمَّن هذه الأذونات الوصول إلى الميكروفون والكاميرا والموقع الجغرافي وغيرها من أذونات الوصول إلى المعلومات الحسّاسة على <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>يمكنك تغيير هذه الأذونات في أي وقت في إعداداتك على <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"رمز التطبيق"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"زر مزيد من المعلومات"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"الهاتف"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"الرسائل القصيرة"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"جهات الاتصال"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"التقويم"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"الأجهزة المجاورة"</string>
<string name="permission_storage" msgid="6831099350839392343">"الصور والوسائط"</string>
<string name="permission_notification" msgid="693762568127741203">"الإشعارات"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"التطبيقات"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"يسمح هذا الإذن بالوصول إلى رقم هاتفك ومعلومات الشبكة. ويجب منح هذا الإذن لإجراء مكالمات وتلقّي بريد صوتي عبر بروتوكول الصوت على الإنترنت وإعادة توجيه المكالمات وتعديل سجلات المكالمات."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"يسمح هذا الإذن بقراءة قائمة جهات الاتصال أو إنشائها أو تعديلها وكذلك قائمة كل الحسابات المُستخدَمة على جهازك."</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"يمكن لهذا الملف الشخصي قراءة جميع الإشعارات، بما في ذلك المعلومات، مثل جهات الاتصال والرسائل والصور."</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"بث تطبيقات هاتفك"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-eu/strings.xml b/packages/CompanionDeviceManager/res/values-eu/strings.xml
index 3140762..66d433d 100644
--- a/packages/CompanionDeviceManager/res/values-eu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-eu/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Eman <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> atzitzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
<string name="profile_name_watch" msgid="576290739483672360">"erlojua"</string>
<string name="chooser_title" msgid="2262294130493605839">"Aukeratu <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> aplikazioak kudeatu beharreko <xliff:g id="PROFILE_NAME">%1$s</xliff:g>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da aplikazioa. Jakinarazpenekin interakzioan aritzeko, eta telefonoa, SMSak, kontaktuak, egutegia, deien erregistroak eta inguruko gailuak erabiltzeko baimenak izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> kudeatzeko behar da aplikazioa. Baimen hauek izango ditu <xliff:g id="APP_NAME">%2$s</xliff:g> aplikazioak:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Eman informazioa telefonotik hartzeko baimena <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> aplikazioari"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Gailu baterako baino gehiagotarako zerbitzuak"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Gailu batetik bestera aplikazioak igortzeko baimena eskatzen ari da <xliff:g id="APP_NAME">%1$s</xliff:g>, <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> gailuaren izenean"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>Haien artean, baliteke <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong> gailuaren mikrofonoa, kamera, kokapenerako sarbidea eta beste kontuzko baimen batzuk egotea.</p> <p>Baimen horiek edonoiz alda ditzakezu <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong> gailuaren ezarpenetan.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Aplikazioaren ikonoa"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Informazio gehiagorako botoia"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Telefonoa"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMSak"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Kontaktuak"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Egutegia"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Inguruko gailuak"</string>
<string name="permission_storage" msgid="6831099350839392343">"Argazkiak eta multimedia-edukia"</string>
<string name="permission_notification" msgid="693762568127741203">"Jakinarazpenak"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Aplikazioak"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Telefono-zenbakia eta sareari buruzko informazioa atzi ditzake. Dei arruntak eta VoIP bidezko deiak egiteko, erantzungailurako, deiak birbideratzeko aukerarako eta deien erregistroan editatzeko behar da."</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Kontaktuen zerrenda irakurri, sortu edo edita dezake, baita kontuan erabilitako kontu guztien zerrenda atzitu ere"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Jakinarazpen guztiak irakur ditzake; besteak beste, kontaktuak, mezuak, argazkiak eta antzeko informazioa"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Igorri zuzenean telefonoko aplikazioak"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-gu/strings.xml b/packages/CompanionDeviceManager/res/values-gu/strings.xml
index 6483d1b..6f5ebea 100644
--- a/packages/CompanionDeviceManager/res/values-gu/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-gu/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"તમારા <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>ને ઍક્સેસ કરવાની <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>ને મંજૂરી આપો"</string>
<string name="profile_name_watch" msgid="576290739483672360">"સ્માર્ટવૉચ"</string>
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> દ્વારા મેનેજ કરવા માટે કોઈ <xliff:g id="PROFILE_NAME">%1$s</xliff:g> પસંદ કરો"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની તેમજ તમારો ફોન, SMS, સંપર્કો, કૅલેન્ડર, કૉલ લૉગ અને નજીકના ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"તમારી <xliff:g id="DEVICE_NAME">%1$s</xliff:g> મેનેજ કરવા માટે ઍપ જરૂરી છે. <xliff:g id="APP_NAME">%2$s</xliff:g>ને આ પરવાનગીઓ સાથે ક્રિયાપ્રતિક્રિયા કરવાની મંજૂરી આપવામાં આવશે:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"તમારા ફોનમાંથી આ માહિતી ઍક્સેસ કરવા માટે, <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong>ને મંજૂરી આપો"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"ક્રોસ-ડિવાઇસ સેવાઓ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> તમારા <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> વતી તમારા ડિવાઇસ વચ્ચે ઍપ સ્ટ્રીમ કરવાની પરવાનગીની વિનંતી કરી રહી છે"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>આમાં <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong> પરના માઇક્રોફોન, કૅમેરા અને સ્થાનના ઍક્સેસ તથા અન્ય સંવેદનશીલ માહિતીની પરવાનગીઓ શામેલ હોઈ શકે છે.</p> <p>તમે <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong> પર તમારા સેટિંગમાં તમે કોઈપણ સમયે આ પરવાનગીઓને બદલી શકો છો.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"ઍપનું આઇકન"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"વધુ માહિતી માટેનું બટન"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ફોન"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"સંપર્કો"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"કૅલેન્ડર"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"નજીકના ડિવાઇસ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ફોટા અને મીડિયા"</string>
<string name="permission_notification" msgid="693762568127741203">"નોટિફિકેશન"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"ઍપ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"તમારો ફોન નંબર અને નેટવર્કની માહિતી ઍક્સેસ કરી શકે છે. કૉલ અને VoIP કૉલ, વૉઇસમેઇલ કરવા, કૉલ રીડાયરેક્ટ કરવા તથા કૉલ લૉગમાં ફેરફાર કરવા માટે જરૂરી છે"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"તમારી સંપર્ક સૂચિ વાંચી, બનાવી શકે છે અથવા તેમાં ફેરફાર કરી શકે છે તેમજ તમારા ડિવાઇસ પર ઉપયોગમાં લેવાતા બધા એકાઉન્ટની સંપર્ક સૂચિને ઍક્સેસ કરી શકે છે"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"સંપર્કો, મેસેજ અને ફોટા જેવી માહિતી સહિતના બધા નોટિફિકેશન વાંચી શકે છે"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"તમારા ફોનની ઍપ સ્ટ્રીમ કરો"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-my/strings.xml b/packages/CompanionDeviceManager/res/values-my/strings.xml
index 87dc08a..271ddee 100644
--- a/packages/CompanionDeviceManager/res/values-my/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-my/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"သင်၏ <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> ကို သုံးရန် <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ကို ခွင့်ပြုပါ"</string>
<string name="profile_name_watch" msgid="576290739483672360">"နာရီ"</string>
<string name="chooser_title" msgid="2262294130493605839">"<strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong> က စီမံခန့်ခွဲရန် <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ကို ရွေးချယ်ပါ"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ သင်၏ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်များ၊ ပြက္ခဒိန်၊ ခေါ်ဆိုမှတ်တမ်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များဆိုင်ရာ ခွင့်ပြုချက်များသုံးရန်၊ အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်။"</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"သင်၏ <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ကို စီမံခန့်ခွဲရန် ဤအက်ပ်လိုအပ်သည်။ ဤခွင့်ပြုချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%2$s</xliff:g> ကို ခွင့်ပြုမည်-"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"<strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ကို သင့်ဖုန်းမှ ဤအချက်အလက် သုံးခွင့်ပြုမည်"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"စက်များကြားသုံး ဝန်ဆောင်မှုများ"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"<xliff:g id="APP_NAME">%1$s</xliff:g> သည် သင်၏စက်များအကြား အက်ပ်များတိုက်ရိုက်လွှင့်ရန် <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> ကိုယ်စား ခွင့်ပြုချက်တောင်းနေသည်"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>၎င်းတွင် မိုက်ခရိုဖုန်း၊ ကင်မရာ၊ တည်နေရာ အသုံးပြုခွင့်အပြင် <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>ပေါ်ရှိ အခြား သတိထားရမည့် ခွင့်ပြုချက်များ ပါဝင်နိုင်သည်။</p> <p>ဤခွင့်ပြုချက်များကို <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>ပေါ်ရှိ သင်၏ဆက်တင်များတွင် အချိန်မရွေးပြောင်းနိုင်သည်။</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"အက်ပ်သင်္ကေတ"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"နောက်ထပ်အချက်အလက်များ ခလုတ်"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"ဖုန်း"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS စာတိုစနစ်"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"အဆက်အသွယ်များ"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"ပြက္ခဒိန်"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"အနီးတစ်ဝိုက်ရှိ စက်များ"</string>
<string name="permission_storage" msgid="6831099350839392343">"ဓာတ်ပုံနှင့် မီဒီယာများ"</string>
<string name="permission_notification" msgid="693762568127741203">"အကြောင်းကြားချက်များ"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"အက်ပ်များ"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"သင့်ဖုန်းနံပါတ်နှင့် ကွန်ရက်အချက်အလက်ကို သုံးနိုင်သည်။ ဖုန်းခေါ်ဆိုခြင်းနှင့် VoIP၊ အသံမေးလ်၊ ခေါ်ဆိုမှု တစ်ဆင့်ပြန်လွှဲခြင်းနှင့် ခေါ်ဆိုမှတ်တမ်းများ တည်းဖြတ်ခြင်းတို့အတွက် လိုအပ်သည်"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"အဆက်အသွယ် စာရင်းကို ဖတ်နိုင်၊ ပြုလုပ်နိုင် (သို့) တည်းဖြတ်နိုင်သည့်ပြင် သင့်စက်တွင်သုံးသော အကောင့်အားလုံး၏စာရင်းကို သုံးနိုင်သည်"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"အဆက်အသွယ်၊ မက်ဆေ့ဂျ်နှင့် ဓာတ်ပုံကဲ့သို့ အချက်အလက်များအပါအဝင် အကြောင်းကြားချက်အားလုံးကို ဖတ်နိုင်သည်"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"သင့်ဖုန်းရှိအက်ပ်များကို တိုက်ရိုက်ဖွင့်နိုင်သည်"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
index d1b8774..1a0d4d9 100644
--- a/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt-rBR/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permitir que o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acesse o dispositivo <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas permissões:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acesse estas informações do smartphone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>Isso pode incluir acesso a microfone, câmera e localização e outras permissões sensíveis no <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>Você pode mudar essas permissões a qualquer momento nas configurações do <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos por perto"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Acessar seu número de telefone e informações da rede. Necessária para chamadas e VoIP, correio de voz, redirecionamento de chamadas e edição de registros de chamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Ler, criar ou editar sua lista de contatos, e também acessar a lista de contatos de todas as contas usadas no seu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-pt/strings.xml b/packages/CompanionDeviceManager/res/values-pt/strings.xml
index d1b8774..1a0d4d9 100644
--- a/packages/CompanionDeviceManager/res/values-pt/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-pt/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Permitir que o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acesse o dispositivo <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_watch" msgid="576290739483672360">"relógio"</string>
<string name="chooser_title" msgid="2262294130493605839">"Escolha um <xliff:g id="PROFILE_NAME">%1$s</xliff:g> para ser gerenciado pelo app <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. O <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, Google Agenda, registros de chamadas e as permissões de dispositivos por perto."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"O app é necessário para gerenciar seu <xliff:g id="DEVICE_NAME">%1$s</xliff:g>. <xliff:g id="APP_NAME">%2$s</xliff:g> vai poder interagir com estas permissões:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Permitir que o app <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> acesse estas informações do smartphone"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Serviços entre dispositivos"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> está pedindo permissão em nome do seu <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> para fazer streaming de apps entre seus dispositivos"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>Isso pode incluir acesso a microfone, câmera e localização e outras permissões sensíveis no <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>Você pode mudar essas permissões a qualquer momento nas configurações do <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Ícone do app"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Botão \"Mais informações\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Smartphone"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Contatos"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Agenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Dispositivos por perto"</string>
<string name="permission_storage" msgid="6831099350839392343">"Fotos e mídia"</string>
<string name="permission_notification" msgid="693762568127741203">"Notificações"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Apps"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Acessar seu número de telefone e informações da rede. Necessária para chamadas e VoIP, correio de voz, redirecionamento de chamadas e edição de registros de chamadas"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Ler, criar ou editar sua lista de contatos, e também acessar a lista de contatos de todas as contas usadas no seu dispositivo"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Pode ler todas as notificações, incluindo informações como contatos, mensagens e fotos"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Fazer transmissão dos apps no seu smartphone"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-sw/strings.xml b/packages/CompanionDeviceManager/res/values-sw/strings.xml
index a1c354f..856dab1 100644
--- a/packages/CompanionDeviceManager/res/values-sw/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-sw/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Ruhusu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ifikie <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong> yako"</string>
<string name="profile_name_watch" msgid="576290739483672360">"saa"</string>
<string name="chooser_title" msgid="2262294130493605839">"Chagua <xliff:g id="PROFILE_NAME">%1$s</xliff:g> ili idhibitiwe na <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia arifa zako na kufikia ruhusa zako za Simu, SMS, Anwani, Kalenda, Rekodi za nambari za simu na Vifaa vilivyo karibu."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Programu hii inahitajika ili udhibiti <xliff:g id="DEVICE_NAME">%1$s</xliff:g> yako. <xliff:g id="APP_NAME">%2$s</xliff:g> itaruhusiwa kufikia ruhusa hizi:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Ruhusu <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> ifikie maelezo haya kutoka kwenye simu yako"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Huduma za kifaa kilichounganishwa kwingine"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Programu ya <xliff:g id="APP_NAME">%1$s</xliff:g> inaomba ruhusa kwa niaba ya <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> yako ili itiririshe programu kati ya vifaa vyako"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>Hii huenda ikajumuisha ufikiaji wa Maikrofoni, Kamera na Mahali, pamoja na ruhusa nyingine nyeti kwenye <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>Unaweza kubadilisha ruhusa hizi muda wowote katika Mipangilio yako kwenye <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Aikoni ya Programu"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Kitufe cha Maelezo Zaidi"</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Simu"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Anwani"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Kalenda"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Vifaa vilivyo karibu"</string>
<string name="permission_storage" msgid="6831099350839392343">"Picha na maudhui"</string>
<string name="permission_notification" msgid="693762568127741203">"Arifa"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Programu"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Inaweza kufikia nambari yako ya simu na maelezo ya mtandao. Inahitajika kwa ajili ya kupiga simu na VoIP, ujumbe wa sauti, uelekezaji wa simu kwingine na kubadilisha rekodi za nambari za simu"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Inaweza kusoma, kuunda au kubadilisha orodha yetu ya anwani na pia kufikia orodha ya akaunti zote zinazotumiwa kwenye kifaa chako"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Inaweza kusoma arifa zote, ikiwa ni pamoja na maelezo kama vile anwani, ujumbe na picha"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Tiririsha programu za simu yako"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CompanionDeviceManager/res/values-uk/strings.xml b/packages/CompanionDeviceManager/res/values-uk/strings.xml
index 905c62a..79b03ea 100644
--- a/packages/CompanionDeviceManager/res/values-uk/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-uk/strings.xml
@@ -20,10 +20,8 @@
<string name="confirmation_title" msgid="3785000297483688997">"Надати додатку <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> доступ до пристрою <strong><xliff:g id="DEVICE_NAME">%2$s</xliff:g></strong>"</string>
<string name="profile_name_watch" msgid="576290739483672360">"годинник"</string>
<string name="chooser_title" msgid="2262294130493605839">"Виберіть <xliff:g id="PROFILE_NAME">%1$s</xliff:g>, яким керуватиме додаток <strong><xliff:g id="APP_NAME">%2$s</xliff:g></strong>"</string>
- <!-- no translation found for summary_watch (4085794790142204006) -->
- <skip />
- <!-- no translation found for summary_watch_single_device (1523091550243476756) -->
- <skip />
+ <string name="summary_watch" msgid="4085794790142204006">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Календар\", \"Журнали викликів\" і \"Пристрої поблизу\"."</string>
+ <string name="summary_watch_single_device" msgid="1523091550243476756">"Цей додаток потрібен, щоб керувати пристроєм \"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>\". Додаток <xliff:g id="APP_NAME">%2$s</xliff:g> зможе взаємодіяти з такими дозволами:"</string>
<string name="title_app_streaming" msgid="2270331024626446950">"Надайте додатку <strong><xliff:g id="APP_NAME">%1$s</xliff:g></strong> доступ до цієї інформації з телефона"</string>
<string name="helper_title_app_streaming" msgid="4151687003439969765">"Сервіси для кількох пристроїв"</string>
<string name="helper_summary_app_streaming" msgid="5977509499890099">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> від імені вашого пристрою <xliff:g id="DEVICE_TYPE">%2$s</xliff:g> запитує дозвіл на трансляцію додатків між вашими пристроями"</string>
@@ -42,29 +40,20 @@
<string name="permission_sync_summary" msgid="4866838188678457084">"<p>Це може бути доступ до мікрофона, камери та геоданих, а також до іншої конфіденційної інформації на пристрої <strong><xliff:g id="COMPANION_DEVICE_NAME_0">%1$s</xliff:g></strong>.</p> <p>Ви можете будь-коли змінити ці дозволи в налаштуваннях на пристрої <strong><xliff:g id="COMPANION_DEVICE_NAME_1">%1$s</xliff:g></strong>.</p>"</string>
<string name="vendor_icon_description" msgid="4445875290032225965">"Значок додатка"</string>
<string name="vendor_header_button_description" msgid="6566660389500630608">"Кнопка \"Докладніше\""</string>
- <!-- no translation found for permission_phone (2661081078692784919) -->
- <skip />
- <!-- no translation found for permission_sms (6337141296535774786) -->
- <skip />
- <!-- no translation found for permission_contacts (3858319347208004438) -->
- <skip />
- <!-- no translation found for permission_calendar (6805668388691290395) -->
- <skip />
- <!-- no translation found for permission_nearby_devices (7530973297737123481) -->
- <skip />
+ <string name="permission_phone" msgid="2661081078692784919">"Телефон"</string>
+ <string name="permission_sms" msgid="6337141296535774786">"SMS"</string>
+ <string name="permission_contacts" msgid="3858319347208004438">"Контакти"</string>
+ <string name="permission_calendar" msgid="6805668388691290395">"Календар"</string>
+ <string name="permission_nearby_devices" msgid="7530973297737123481">"Пристрої поблизу"</string>
<string name="permission_storage" msgid="6831099350839392343">"Фотографії та медіафайли"</string>
<string name="permission_notification" msgid="693762568127741203">"Сповіщення"</string>
- <!-- no translation found for permission_app_streaming (6009695219091526422) -->
- <skip />
- <!-- no translation found for permission_phone_summary (6154198036705702389) -->
- <skip />
+ <string name="permission_app_streaming" msgid="6009695219091526422">"Додатки"</string>
+ <string name="permission_phone_summary" msgid="6154198036705702389">"Може переглядати ваш номер телефону й інформацію про мережу. Потрібно для здійснення викликів і зв’язку через VoIP, голосової пошти, переадресації викликів і редагування журналів викликів"</string>
<string name="permission_sms_summary" msgid="5107174184224165570"></string>
- <!-- no translation found for permission_contacts_summary (7850901746005070792) -->
- <skip />
+ <string name="permission_contacts_summary" msgid="7850901746005070792">"Може читати, створювати або редагувати список контактів, а також переглядати список усіх облікових записів, які використовуються на вашому пристрої"</string>
<string name="permission_calendar_summary" msgid="9070743747408808156"></string>
<string name="permission_nearby_devices_summary" msgid="8587497797301075494"></string>
<string name="permission_notification_summary" msgid="884075314530071011">"Може читати всі сповіщення, зокрема таку інформацію, як контакти, повідомлення та фотографії"</string>
- <!-- no translation found for permission_app_streaming_summary (606923325679670624) -->
- <skip />
+ <string name="permission_app_streaming_summary" msgid="606923325679670624">"Транслювати додатки телефона"</string>
<string name="permission_storage_summary" msgid="3918240895519506417"></string>
</resources>
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
new file mode 100644
index 0000000..388d098
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
Binary files differ
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
new file mode 100644
index 0000000..9e4f424
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ 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"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="VectorPath"
+ android:name="vector"
+ android:width="21dp"
+ android:height="17dp"
+ android:viewportWidth="21"
+ android:viewportHeight="17">
+ <path
+ android:name="path"
+ android:pathData="M 4 2.941 L 20 2.941 L 20 0.941 L 4 0.941 C 2.9 0.941 2 1.841 2 2.941 L 2 13.941 L 0 13.941 L 0 16.941 L 11 16.941 L 11 13.941 L 4 13.941 L 4 2.941 Z M 20 4.941 L 14 4.941 C 13.45 4.941 13 5.391 13 5.941 L 13 15.941 C 13 16.491 13.45 16.941 14 16.941 L 20 16.941 C 20.55 16.941 21 16.491 21 15.941 L 21 5.941 C 21 5.391 20.55 4.941 20 4.941 Z M 15 13.941 L 19 13.941 L 19 6.941 L 15 6.941 L 15 13.941 Z"
+ android:fillColor="#5f6368"
+ android:strokeWidth="1"
+ android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
new file mode 100644
index 0000000..b6ee4f9
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
@@ -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.
+ -->
+
+<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="19dp"
+ android:height="21dp"
+ android:viewportWidth="19"
+ android:viewportHeight="21">
+ <path
+ android:name="path"
+ android:pathData="M 0.25 8.591 C 0.133 8.508 0.058 8.408 0.025 8.291 C 0.008 8.158 0.05 8.025 0.15 7.891 C 1.183 6.475 2.475 5.375 4.025 4.591 C 5.592 3.808 7.258 3.416 9.025 3.416 C 10.792 3.416 12.458 3.8 14.025 4.566 C 15.592 5.316 16.9 6.408 17.95 7.841 C 18.067 7.991 18.1 8.125 18.05 8.241 C 18.017 8.358 17.95 8.458 17.85 8.541 C 17.75 8.625 17.633 8.666 17.5 8.666 C 17.367 8.65 17.25 8.575 17.15 8.441 C 16.233 7.141 15.05 6.15 13.6 5.466 C 12.167 4.766 10.642 4.416 9.025 4.416 C 7.408 4.416 5.892 4.766 4.475 5.466 C 3.058 6.15 1.883 7.141 0.95 8.441 C 0.85 8.591 0.733 8.675 0.6 8.691 C 0.467 8.708 0.35 8.675 0.25 8.591 Z M 11.85 20.916 C 10.117 20.483 8.7 19.625 7.6 18.341 C 6.5 17.041 5.95 15.458 5.95 13.591 C 5.95 12.758 6.25 12.058 6.85 11.491 C 7.45 10.925 8.175 10.641 9.025 10.641 C 9.875 10.641 10.6 10.925 11.2 11.491 C 11.8 12.058 12.1 12.758 12.1 13.591 C 12.1 14.141 12.308 14.608 12.725 14.991 C 13.142 15.358 13.633 15.541 14.2 15.541 C 14.767 15.541 15.25 15.358 15.65 14.991 C 16.05 14.608 16.25 14.141 16.25 13.591 C 16.25 11.658 15.542 10.033 14.125 8.716 C 12.708 7.4 11.017 6.741 9.05 6.741 C 7.083 6.741 5.392 7.4 3.975 8.716 C 2.558 10.033 1.85 11.65 1.85 13.566 C 1.85 13.966 1.883 14.466 1.95 15.066 C 2.033 15.666 2.217 16.366 2.5 17.166 C 2.55 17.316 2.542 17.45 2.475 17.566 C 2.425 17.683 2.333 17.766 2.2 17.816 C 2.067 17.866 1.933 17.866 1.8 17.816 C 1.683 17.75 1.6 17.65 1.55 17.516 C 1.3 16.866 1.117 16.225 1 15.591 C 0.9 14.941 0.85 14.275 0.85 13.591 C 0.85 11.375 1.65 9.516 3.25 8.016 C 4.867 6.516 6.792 5.766 9.025 5.766 C 11.275 5.766 13.208 6.516 14.825 8.016 C 16.442 9.516 17.25 11.375 17.25 13.591 C 17.25 14.425 16.95 15.125 16.35 15.691 C 15.767 16.241 15.05 16.516 14.2 16.516 C 13.35 16.516 12.617 16.241 12 15.691 C 11.4 15.125 11.1 14.425 11.1 13.591 C 11.1 13.041 10.892 12.583 10.475 12.216 C 10.075 11.833 9.592 11.641 9.025 11.641 C 8.458 11.641 7.967 11.833 7.55 12.216 C 7.15 12.583 6.95 13.041 6.95 13.591 C 6.95 15.208 7.425 16.558 8.375 17.641 C 9.342 18.725 10.583 19.483 12.1 19.916 C 12.25 19.966 12.35 20.05 12.4 20.166 C 12.45 20.283 12.458 20.408 12.425 20.541 C 12.392 20.658 12.325 20.758 12.225 20.841 C 12.125 20.925 12 20.95 11.85 20.916 Z M 3.5 3.366 C 3.367 3.45 3.233 3.475 3.1 3.441 C 2.967 3.391 2.867 3.3 2.8 3.166 C 2.733 3.033 2.717 2.916 2.75 2.816 C 2.783 2.7 2.867 2.6 3 2.516 C 3.933 2.016 4.908 1.633 5.925 1.366 C 6.942 1.1 7.975 0.966 9.025 0.966 C 10.092 0.966 11.133 1.1 12.15 1.366 C 13.167 1.616 14.15 1.983 15.1 2.466 C 15.25 2.55 15.333 2.65 15.35 2.766 C 15.383 2.883 15.375 3 15.325 3.116 C 15.275 3.233 15.192 3.325 15.075 3.391 C 14.958 3.458 14.817 3.45 14.65 3.366 C 13.767 2.916 12.85 2.575 11.9 2.341 C 10.967 2.091 10.008 1.966 9.025 1.966 C 8.058 1.966 7.108 2.083 6.175 2.316 C 5.242 2.533 4.35 2.883 3.5 3.366 Z M 6.45 20.566 C 5.467 19.533 4.708 18.483 4.175 17.416 C 3.658 16.333 3.4 15.058 3.4 13.591 C 3.4 12.075 3.95 10.8 5.05 9.766 C 6.15 8.716 7.475 8.191 9.025 8.191 C 10.575 8.191 11.908 8.716 13.025 9.766 C 14.142 10.8 14.7 12.075 14.7 13.591 C 14.7 13.741 14.65 13.866 14.55 13.966 C 14.467 14.05 14.35 14.091 14.2 14.091 C 14.067 14.091 13.95 14.05 13.85 13.966 C 13.75 13.866 13.7 13.741 13.7 13.591 C 13.7 12.341 13.233 11.3 12.3 10.466 C 11.383 9.616 10.292 9.191 9.025 9.191 C 7.758 9.191 6.667 9.616 5.75 10.466 C 4.85 11.3 4.4 12.341 4.4 13.591 C 4.4 14.941 4.633 16.091 5.1 17.041 C 5.567 17.975 6.25 18.916 7.15 19.866 C 7.25 19.966 7.3 20.083 7.3 20.216 C 7.3 20.35 7.25 20.466 7.15 20.566 C 7.05 20.666 6.933 20.716 6.8 20.716 C 6.667 20.716 6.55 20.666 6.45 20.566 Z M 14 18.866 C 12.517 18.866 11.225 18.366 10.125 17.366 C 9.042 16.366 8.5 15.108 8.5 13.591 C 8.5 13.458 8.542 13.341 8.625 13.241 C 8.725 13.141 8.85 13.091 9 13.091 C 9.15 13.091 9.267 13.141 9.35 13.241 C 9.45 13.341 9.5 13.458 9.5 13.591 C 9.5 14.841 9.95 15.866 10.85 16.666 C 11.75 17.466 12.8 17.866 14 17.866 C 14.1 17.866 14.242 17.858 14.425 17.841 C 14.608 17.825 14.8 17.8 15 17.766 C 15.15 17.733 15.275 17.758 15.375 17.841 C 15.492 17.908 15.567 18.016 15.6 18.166 C 15.633 18.3 15.608 18.416 15.525 18.516 C 15.442 18.616 15.333 18.683 15.2 18.716 C 14.9 18.8 14.633 18.85 14.4 18.866 L 14 18.866 Z"
+ android:fillColor="#5e6144"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
new file mode 100644
index 0000000..61800f1
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
@@ -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.
+ -->
+
+<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="17dp"
+ android:viewportWidth="20"
+ android:viewportHeight="17">
+ <path
+ android:name="path"
+ android:pathData="M 2 16.941 C 1.45 16.941 0.975 16.75 0.575 16.366 C 0.192 15.966 0 15.491 0 14.941 L 0 2.941 C 0 2.391 0.192 1.925 0.575 1.541 C 0.975 1.141 1.45 0.941 2 0.941 L 18 0.941 C 18.55 0.941 19.017 1.141 19.4 1.541 C 19.8 1.925 20 2.391 20 2.941 L 20 14.941 C 20 15.491 19.8 15.966 19.4 16.366 C 19.017 16.75 18.55 16.941 18 16.941 L 2 16.941 Z M 4.5 11.941 L 5.65 11.941 L 5.65 5.941 L 4.75 5.941 L 3 7.191 L 3.6 8.091 L 4.5 7.441 L 4.5 11.941 Z M 7.6 11.941 L 11.5 11.941 L 11.5 10.941 L 9.15 10.941 L 9.1 10.891 C 9.45 10.558 9.733 10.275 9.95 10.041 C 10.183 9.808 10.367 9.625 10.5 9.491 C 10.8 9.191 11.025 8.891 11.175 8.591 C 11.325 8.291 11.4 7.975 11.4 7.641 C 11.4 7.158 11.217 6.758 10.85 6.441 C 10.483 6.108 10.017 5.941 9.45 5.941 C 9.017 5.941 8.625 6.066 8.275 6.316 C 7.925 6.566 7.683 6.891 7.55 7.291 L 8.55 7.691 C 8.633 7.475 8.75 7.308 8.9 7.191 C 9.067 7.058 9.25 6.991 9.45 6.991 C 9.7 6.991 9.9 7.058 10.05 7.191 C 10.217 7.325 10.3 7.491 10.3 7.691 C 10.3 7.875 10.267 8.05 10.2 8.216 C 10.133 8.366 9.983 8.558 9.75 8.791 L 8.95 9.591 C 8.6 9.941 8.15 10.391 7.6 10.941 L 7.6 11.941 Z M 15 11.941 C 15.6 11.941 16.083 11.775 16.45 11.441 C 16.817 11.108 17 10.675 17 10.141 C 17 9.841 16.917 9.575 16.75 9.341 C 16.583 9.108 16.35 8.925 16.05 8.791 L 16.05 8.741 C 16.283 8.608 16.467 8.441 16.6 8.241 C 16.733 8.025 16.8 7.775 16.8 7.491 C 16.8 7.041 16.625 6.675 16.275 6.391 C 15.925 6.091 15.483 5.941 14.95 5.941 C 14.533 5.941 14.142 6.066 13.775 6.316 C 13.425 6.55 13.2 6.841 13.1 7.191 L 14.1 7.591 C 14.167 7.391 14.275 7.233 14.425 7.116 C 14.575 7 14.75 6.941 14.95 6.941 C 15.167 6.941 15.342 7.008 15.475 7.141 C 15.625 7.258 15.7 7.408 15.7 7.591 C 15.7 7.825 15.617 8.008 15.45 8.141 C 15.283 8.275 15.067 8.341 14.8 8.341 L 14.35 8.341 L 14.35 9.341 L 14.85 9.341 C 15.183 9.341 15.442 9.408 15.625 9.541 C 15.808 9.675 15.9 9.858 15.9 10.091 C 15.9 10.308 15.808 10.5 15.625 10.666 C 15.442 10.816 15.233 10.891 15 10.891 C 14.717 10.891 14.5 10.833 14.35 10.716 C 14.2 10.583 14.067 10.358 13.95 10.041 L 12.95 10.441 C 13.067 10.925 13.3 11.3 13.65 11.566 C 14.017 11.816 14.467 11.941 15 11.941 Z M 2 14.941 L 18 14.941 L 18 2.941 L 2 2.941 L 2 14.941 Z M 2 14.941 L 2 2.941 L 2 14.941 Z"
+ android:fillColor="#5e6144"
+ android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index 91771b3..377c13f 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Eiebewysbestuurder"</string>
<string name="string_cancel" msgid="6369133483981306063">"Kanselleer"</string>
<string name="string_continue" msgid="1346732695941131882">"Gaan voort"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Skep op ’n ander plek"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gebruik <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> vir al jou aanmeldings?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> wagwoordsleutels"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> wagwoordsleutels"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Wagwoordsleutel"</string>
<string name="another_device" msgid="5147276802037801217">"’n Ander toestel"</string>
<string name="other_password_manager" msgid="565790221427004141">"Ander wagwoordbestuurders"</string>
<string name="close_sheet" msgid="1393792015338908262">"Maak sigblad toe"</string>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index e77b1a7..b80fe2c 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -48,6 +48,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<!-- no translation found for use_provider_for_all_title (4201020195058980757) -->
<skip />
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index 4af875c..a5c85c5 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"الإنشاء في مكان آخر"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"الحفظ في مكان آخر"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"استخدام جهاز آخر"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"الحفظ على جهاز آخر"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"طريقة بسيطة لتسجيل الدخول بأمان"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"استخدِم بصمة إصبعك أو وجهك أو قفل الشاشة لتسجيل الدخول باستخدام مفتاح مرور فريد لا يمكن نسيانه أو سرقته. مزيد من المعلومات"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"اختيار مكان <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -22,7 +21,7 @@
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"هل تريد إنشاء مفتاح مرور في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"هل تريد حفظ كلمة مرورك في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"هل تريد حفظ معلومات تسجيل الدخول في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
- <string name="choose_create_option_description" msgid="4419171903963100257">"يمكنك استخدام <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> على أي جهاز. يتم حفظه في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" لـ \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\"."</string>
+ <string name="choose_create_option_description" msgid="4419171903963100257">"يمكنك استخدام <xliff:g id="TYPE">%2$s</xliff:g> الخاص بـ \"<xliff:g id="APPDOMAINNAME">%1$s</xliff:g>\" على أي جهاز. ويتم حفظه في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g>\" للحساب \"<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>\"."</string>
<string name="passkey" msgid="632353688396759522">"مفتاح مرور"</string>
<string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
<string name="sign_ins" msgid="4710739369149469208">"عمليات تسجيل الدخول"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"هل تريد استخدام \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" لكل عمليات تسجيل الدخول؟"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index c104098..4d0ba68 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
<string name="string_cancel" msgid="6369133483981306063">"বাতিল কৰক"</string>
<string name="string_continue" msgid="1346732695941131882">"অব্যাহত ৰাখক"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য ঠাইত সৃষ্টি কৰক"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য ঠাইত ছেভ কৰক"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইচ ব্যৱহাৰ কৰক"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য এটা ডিভাইচত ছেভ কৰক"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"সুৰক্ষিতভাৱে ছাইন ইন কৰাৰ এক সৰল উপায়"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"পাহৰি নোযোৱা অথবা চুৰি কৰিব নোৱৰা এটা অদ্বিতীয় পাছকী ব্যৱহাৰ কৰি ছাইন ইন কৰিবলৈ আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক। অধিক জানক"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"ক’ত <xliff:g id="CREATETYPES">%1$s</xliff:g> সেয়া বাছনি কৰক"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"আপোনাৰ আটাইবোৰ ছাইন ইনৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যৱহাৰ কৰিবনে?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> টা পাছকী"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> টা পাছকী"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"পাছকী"</string>
<string name="another_device" msgid="5147276802037801217">"অন্য এটা ডিভাইচ"</string>
<string name="other_password_manager" msgid="565790221427004141">"অন্য পাছৱৰ্ড পৰিচালক"</string>
<string name="close_sheet" msgid="1393792015338908262">"শ্বীট বন্ধ কৰক"</string>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index 4a8fef5..14313f7 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Giriş Məlumatları Meneceri"</string>
<string name="string_cancel" msgid="6369133483981306063">"Ləğv edin"</string>
<string name="string_continue" msgid="1346732695941131882">"Davam edin"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Başqa yerdə yaradın"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Bütün girişlər üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> istifadə edilsin?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> giriş açarı"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> giriş açarı"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Giriş açarı"</string>
<string name="another_device" msgid="5147276802037801217">"Digər cihaz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Digər parol menecerləri"</string>
<string name="close_sheet" msgid="1393792015338908262">"Səhifəni bağlayın"</string>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index b46518e..c58ec14 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index afa4d01..3c23afd 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Выкарыстоўваць папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" для ўсіх спосабаў уваходу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index 1a2f881..af7eb17 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се използва ли <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за всичките ви данни за вход?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 3257b78..152e5bd 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"আপনার সব সাইন-ইনের জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যবহার করবেন?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index 705cfef..d774b88 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Upravitelj akreditiva"</string>
<string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
<string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Kreirajte na drugom mjestu"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Koristiti uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve vaše prijave?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pristupni ključ"</string>
<string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
<string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji lozinki"</string>
<string name="close_sheet" msgid="1393792015338908262">"Zatvaranje tabele"</string>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index d9b5a90..bfd9164 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crea en un altre lloc"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Desa en un altre lloc"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Utilitza un altre dispositiu"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Desa en un altre dispositiu"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Una manera senzilla i segura d\'iniciar la sessió"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilitza l\'empremta digital, la cara o el bloqueig de pantalla per iniciar la sessió amb una clau d\'accés única que no es pot oblidar ni robar. Més informació"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Tria on vols <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vols utilitzar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per a tots els teus inicis de sessió?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index e7fe5365..72a5f98 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Používat <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pro všechna přihlášení?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index 86cf9ff..a05137e 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruge <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> til alle dine loginmetoder?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index d239dd3..fa1e8f1 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Anmeldedaten-Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Abbrechen"</string>
<string name="string_continue" msgid="1346732695941131882">"Weiter"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"An anderem Speicherort erstellen"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> für alle Anmeldungen verwenden?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> Passkeys"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> Passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Ein anderes Gerät"</string>
<string name="other_password_manager" msgid="565790221427004141">"Andere Passwortmanager"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tabellenblatt schließen"</string>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 9b7ccbb..706d9f3 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Ακύρωση"</string>
<string name="string_continue" msgid="1346732695941131882">"Συνέχεια"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Δημιουργία σε άλλη θέση"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Να χρησιμοποιηθεί το <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> για όλες τις συνδέσεις σας;"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> κλειδιά πρόσβασης"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> κλειδιά πρόσβασης"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Κλειδί πρόσβασης"</string>
<string name="another_device" msgid="5147276802037801217">"Άλλη συσκευή"</string>
<string name="other_password_manager" msgid="565790221427004141">"Άλλοι διαχειριστές κωδικών πρόσβασης"</string>
<string name="close_sheet" msgid="1393792015338908262">"Κλείσιμο φύλλου"</string>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index 682dffb..7adeded 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
<string name="string_continue" msgid="1346732695941131882">"Continue"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
@@ -12,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<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" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Another device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index 4ec2872..8a8b884 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -11,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,15 +22,12 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index 682dffb..7adeded 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
<string name="string_continue" msgid="1346732695941131882">"Continue"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
@@ -12,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<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" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Another device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index 682dffb..7adeded 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancel"</string>
<string name="string_continue" msgid="1346732695941131882">"Continue"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Create in another place"</string>
@@ -12,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<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" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkeys"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Another device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Other password managers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Close sheet"</string>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index d114a46..85e94df 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -11,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
<string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"save your sign-in info"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Set a default password manager to save your passwords and passkeys and sign in faster next time."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Create a passkey in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Save your password to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Save your sign-in info to <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,15 +22,12 @@
<string name="passkey" msgid="632353688396759522">"passkey"</string>
<string name="password" msgid="6738570945182936667">"password"</string>
<string name="sign_ins" msgid="4710739369149469208">"sign-ins"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Create passkey in"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Save password to"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Save sign-in to"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Create a passkey in another device?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Use <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for all your sign-ins?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"This password manager will store your passwords and passkeys to help you easily sign in."</string>
<string name="set_as_default" msgid="4415328591568654603">"Set as default"</string>
<string name="use_once" msgid="9027366575315399714">"Use once"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passwords, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkeys"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index 96e7697..5b8e442 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otra ubicación"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Quieres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus accesos?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> llaves de acceso, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> contraseñas"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Llave de acceso"</string>
<string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Otros administradores de contraseñas"</string>
<string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index dce1a8e..19fde72 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus inicios de sesión?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 00396e0..5b1b070 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Kas kasutada teenust <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kõigi teie sisselogimisandmete puhul?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index 38f6592..b2c1fe5 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> erabili nahi duzu kredentzial guztietarako?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index fdfd1e3..98b487c 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"از <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> برای همه ورود به سیستمها استفاده شود؟"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 26cfbe5..9ad178a 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Luo muualla"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Tallenna muualle"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Käytä toista laitetta"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Tallenna toiselle laitteelle"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Helppo tapa kirjautua turvallisesti sisään"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Käytä sormenjälkeä, kasvoja tai näytön lukitusta, niin voit kirjautua sisään yksilöllisellä avainkoodilla, jota ei voi unohtaa tai varastaa. Lue lisää"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Valitse paikka: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Otetaanko <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> käyttöön kaikissa sisäänkirjautumisissa?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index ef3d325..a2e7581 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Créer à un autre emplacement"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string>
<string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string>
<string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index f660dde..2b280fb 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annuler"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuer"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Créer ailleurs"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string>
<string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string>
<string name="other_password_manager" msgid="565790221427004141">"Autres gestionnaires de mots de passe"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fermer la feuille"</string>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index cacec21..cc03ca4 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Xestor de credenciais"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crear noutro lugar"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Gardar noutro lugar"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Gardar noutro dispositivo"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Un xeito fácil de iniciar sesión de forma segura"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa a impresión dixital, a cara ou o bloqueo de pantalla para iniciar sesión cunha clave de acceso única que non podes esquecer nin cha poden roubar. Máis información"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Escolle onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Queres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cada vez que inicies sesión?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claves de acceso"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claves de acceso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clave de acceso"</string>
<string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Outros xestores de contrasinais"</string>
<string name="close_sheet" msgid="1393792015338908262">"Pechar folla"</string>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index 7ac70aa..f796d20 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"લૉગ ઇન વિગતોના મેનેજર"</string>
<string name="string_cancel" msgid="6369133483981306063">"રદ કરો"</string>
<string name="string_continue" msgid="1346732695941131882">"ચાલુ રાખો"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"કોઈ અન્ય સ્થાન પર બનાવો"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"શું તમારા બધા સાઇન-ઇન માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>નો ઉપયોગ કરીએ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> પાસકી"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> પાસકી"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"પાસકી"</string>
<string name="another_device" msgid="5147276802037801217">"કોઈ અન્ય ડિવાઇસ"</string>
<string name="other_password_manager" msgid="565790221427004141">"અન્ય પાસવર્ડ મેનેજર"</string>
<string name="close_sheet" msgid="1393792015338908262">"શીટ બંધ કરો"</string>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index 8d28e0f..fbf1c02 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"क्या आपको साइन इन से जुड़ी सारी जानकारी सेव करने के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> का इस्तेमाल करना है?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index 06db583..6c1952f 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite li upotrebljavati uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve prijave?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 738de3a..0efa3e8 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Tanúsítványkezelő"</string>
<string name="string_cancel" msgid="6369133483981306063">"Mégse"</string>
<string name="string_continue" msgid="1346732695941131882">"Folytatás"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Létrehozás másik helyen"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Szeretné a következőt használni az összes bejelentkezési adatához: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> azonosítókulcs"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> azonosítókulcs"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Azonosítókulcs"</string>
<string name="another_device" msgid="5147276802037801217">"Másik eszköz"</string>
<string name="other_password_manager" msgid="565790221427004141">"Egyéb jelszókezelők"</string>
<string name="close_sheet" msgid="1393792015338908262">"Munkalap bezárása"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index 7320e64..de47e9f 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Միշտ մուտք գործե՞լ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածի միջոցով"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index 827a4ff..d980d44 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Pengelola Kredensial"</string>
<string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
<string name="string_continue" msgid="1346732695941131882">"Lanjutkan"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua info login Anda?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci sandi"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> kunci sandi"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci sandi"</string>
<string name="another_device" msgid="5147276802037801217">"Perangkat lain"</string>
<string name="other_password_manager" msgid="565790221427004141">"Pengelola sandi lainnya"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tutup sheet"</string>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index c52e5f7..3fd6af2 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Nota <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> fyrir allar innskráningar?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index a06135e..3a7b0fb 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gestore delle credenziali"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annulla"</string>
<string name="string_continue" msgid="1346732695941131882">"Continua"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Crea in un altro luogo"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vuoi usare <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per tutti gli accessi?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Un altro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Altri gestori delle password"</string>
<string name="close_sheet" msgid="1393792015338908262">"Chiudi il foglio"</string>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index e9c6adb7..397ad60 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"מנהל פרטי הכניסה"</string>
<string name="string_cancel" msgid="6369133483981306063">"ביטול"</string>
<string name="string_continue" msgid="1346732695941131882">"המשך"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"יצירה במקום אחר"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"להשתמש ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> בכל הכניסות?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> מפתחות גישה"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> מפתחות גישה"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"מפתח גישה"</string>
<string name="another_device" msgid="5147276802037801217">"מכשיר אחר"</string>
<string name="other_password_manager" msgid="565790221427004141">"מנהלי סיסמאות אחרים"</string>
<string name="close_sheet" msgid="1393792015338908262">"סגירת הגיליון"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 8e448eb..0340b66 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"認証情報マネージャー"</string>
<string name="string_cancel" msgid="6369133483981306063">"キャンセル"</string>
<string name="string_continue" msgid="1346732695941131882">"続行"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"別の場所で作成"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ログインのたびに <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> を使用しますか?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード、<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 件のパスキー"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 件のパスキー"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"パスキー"</string>
<string name="another_device" msgid="5147276802037801217">"別のデバイス"</string>
<string name="other_password_manager" msgid="565790221427004141">"他のパスワード マネージャー"</string>
<string name="close_sheet" msgid="1393792015338908262">"シートを閉じます"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index 853ea19..3da7ea3 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"ავტორიზაციის მონაცემების მმართველი"</string>
<string name="string_cancel" msgid="6369133483981306063">"გაუქმება"</string>
<string name="string_continue" msgid="1346732695941131882">"გაგრძელება"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"სხვა სივრცეში შექმნა"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"გსურთ, გამოიყენოთ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> სისტემაში ყველა შესვლისთვის?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> წვდომის გასაღები"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> წვდომის გასაღები"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"წვდომის გასაღები"</string>
<string name="another_device" msgid="5147276802037801217">"სხვა მოწყობილობა"</string>
<string name="other_password_manager" msgid="565790221427004141">"პაროლების სხვა მმართველები"</string>
<string name="close_sheet" msgid="1393792015338908262">"ფურცლის დახურვა"</string>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 2271533..9491f8e 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Басқа орында жасау"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Басқа орынға сақтау"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Басқа құрылғыны пайдалану"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Басқа құрылғыға сақтау"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Қауіпсіз кірудің оңай жолы"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ұмытылмайтын немесе ұрланбайтын бірегей кіру кілтінің көмегімен кіру үшін саусақ ізін, бетті анықтау функциясын немесе экран құлпын пайдаланыңыз. Толық ақпарат"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> таңдау"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Барлық кіру әрекеті үшін <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> пайдаланылсын ба?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index d517810..80167fc 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ប្រើ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> សម្រាប់ការចូលគណនីទាំងអស់របស់អ្នកឬ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 763f8ee..96304ac 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"ರುಜುವಾತು ನಿರ್ವಾಹಕ"</string>
<string name="string_cancel" msgid="6369133483981306063">"ರದ್ದುಗೊಳಿಸಿ"</string>
<string name="string_continue" msgid="1346732695941131882">"ಮುಂದುವರಿಸಿ"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ರಚಿಸಿ"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ನಿಮ್ಮ ಎಲ್ಲಾ ಸೈನ್-ಇನ್ಗಳಿಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಅನ್ನು ಬಳಸುವುದೇ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್ವರ್ಡ್ಗಳು, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ಪಾಸ್ಕೀಗಳು"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್ವರ್ಡ್ಗಳು"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ಪಾಸ್ಕೀಗಳು"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"ಪಾಸ್ಕೀ"</string>
<string name="another_device" msgid="5147276802037801217">"ಮತ್ತೊಂದು ಸಾಧನ"</string>
<string name="other_password_manager" msgid="565790221427004141">"ಇತರ ಪಾಸ್ವರ್ಡ್ ನಿರ್ವಾಹಕರು"</string>
<string name="close_sheet" msgid="1393792015338908262">"ಶೀಟ್ ಮುಚ್ಚಿರಿ"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 246790d..58518c1 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"모든 로그인에 <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index 3dc7dea..298657e 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Башка жерде түзүү"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Башка жерге сактоо"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Башка түзмөк колдонуу"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Башка түзмөккө сактоо"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Коопсуз кирүүнүн жөнөкөй жолу"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Унутуп калууга же уурдатууга мүмкүн эмес болгон уникалдуу ачкыч менен манжа изин, жүзүнөн таанып ачуу же экранды кулпулоо функцияларын колдонуп өзүңүздү ырастай аласыз. Кененирээк"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> үчүн жер тандаңыз"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> бардык аккаунттарга кирүү үчүн колдонулсунбу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 8efc8a8..215262b 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ໃຊ້ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ສຳລັບການເຂົ້າສູ່ລະບົບທັງໝົດຂອງທ່ານບໍ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index 02d3783..6125fe3 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Prisijungimo duomenų tvarkytuvė"</string>
<string name="string_cancel" msgid="6369133483981306063">"Atšaukti"</string>
<string name="string_continue" msgid="1346732695941131882">"Tęsti"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Sukurti kitoje vietoje"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Naudoti <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> visada prisijungiant?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, „passkey“: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"„passkey“: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Slaptažodis"</string>
<string name="another_device" msgid="5147276802037801217">"Kitas įrenginys"</string>
<string name="other_password_manager" msgid="565790221427004141">"Kitos slaptažodžių tvarkyklės"</string>
<string name="close_sheet" msgid="1393792015338908262">"Uždaryti lapą"</string>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index cbea91a..43c036f 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Akreditācijas datu pārvaldnieks"</string>
<string name="string_cancel" msgid="6369133483981306063">"Atcelt"</string>
<string name="string_continue" msgid="1346732695941131882">"Turpināt"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Izveidot citur"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Saglabāt citur"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Izmantot citu ierīci"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Saglabāt citā ierīcē"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Vienkāršs veids, kā droši pierakstīties"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Izmantojiet pirksta nospiedumu, autorizāciju pēc sejas vai ekrāna bloķēšanu, lai pierakstītos ar unikālu piekļuves atslēgu, ko nevar aizmirst vai nozagt. Uzziniet vairāk."</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Izvēlieties, kur: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vai vienmēr izmantot <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>, lai pierakstītos?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> piekļuves atslēgas"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> piekļuves atslēgas"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Piekļuves atslēga"</string>
<string name="another_device" msgid="5147276802037801217">"Cita ierīce"</string>
<string name="other_password_manager" msgid="565790221427004141">"Citi paroļu pārvaldnieki"</string>
<string name="close_sheet" msgid="1393792015338908262">"Aizvērt lapu"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index e98bfc4..059f042 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Управник на акредитиви"</string>
<string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
<string name="string_continue" msgid="1346732695941131882">"Продолжи"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Создајте на друго место"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се користи <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за сите ваши најавувања?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> криптографски клучеви"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> криптографски клучеви"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Криптографски клуч"</string>
<string name="another_device" msgid="5147276802037801217">"Друг уред"</string>
<string name="other_password_manager" msgid="565790221427004141">"Други управници со лозинки"</string>
<string name="close_sheet" msgid="1393792015338908262">"Затворете го листот"</string>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index 34029ce..e4f6d69 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"ക്രെഡൻഷ്യൽ മാനേജർ"</string>
<string name="string_cancel" msgid="6369133483981306063">"റദ്ദാക്കുക"</string>
<string name="string_continue" msgid="1346732695941131882">"തുടരുക"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"മറ്റൊരു സ്ഥലത്ത് സൃഷ്ടിക്കുക"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"നിങ്ങളുടെ എല്ലാ സൈൻ ഇന്നുകൾക്കും <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്വേഡുകൾ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> പാസ്കീകൾ"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്വേഡുകൾ"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> പാസ്കീകൾ"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"പാസ്കീ"</string>
<string name="another_device" msgid="5147276802037801217">"മറ്റൊരു ഉപകരണം"</string>
<string name="other_password_manager" msgid="565790221427004141">"മറ്റ് പാസ്വേഡ് മാനേജർമാർ"</string>
<string name="close_sheet" msgid="1393792015338908262">"ഷീറ്റ് അടയ്ക്കുക"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index f090931..3f8d4ca 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Мандат үнэмлэхийн менежер"</string>
<string name="string_cancel" msgid="6369133483981306063">"Цуцлах"</string>
<string name="string_continue" msgid="1346732695941131882">"Үргэлжлүүлэх"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Өөр газар үүсгэх"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-г бүх нэвтрэлтдээ ашиглах уу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> passkey"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Өөр төхөөрөмж"</string>
<string name="other_password_manager" msgid="565790221427004141">"Нууц үгний бусад менежер"</string>
<string name="close_sheet" msgid="1393792015338908262">"Хүснэгтийг хаах"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index c4d12f5..aa6f253 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"तुमच्या सर्व साइन-इन साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>वापरायचे का?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index fb130fe..d5f8c0e 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Pengurus Bukti Kelayakan"</string>
<string name="string_cancel" msgid="6369133483981306063">"Batal"</string>
<string name="string_continue" msgid="1346732695941131882">"Teruskan"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Buat di tempat lain"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua log masuk anda?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, kunci laluan <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Kunci laluan <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kunci laluan"</string>
<string name="another_device" msgid="5147276802037801217">"Peranti lain"</string>
<string name="other_password_manager" msgid="565790221427004141">"Password Manager lain"</string>
<string name="close_sheet" msgid="1393792015338908262">"Tutup helaian"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index b14960a..eda2f741 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"သင်၏လက်မှတ်ထိုးဝင်မှု အားလုံးအတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> သုံးမလား။"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index d53bc7e..82854b8 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Opprett på et annet sted"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Lagre på et annet sted"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Bruk en annen enhet"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Lagre på en annen enhet"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"En enkel og trygg påloggingsmåte"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Bruk fingeravtrykk, ansiktet eller en skjermlås til å logge på med en unik tilgangsnøkkel du verken kan glemme eller miste. Finn ut mer"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Velg hvor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for alle pålogginger?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index 77b0959..23f4f43 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"तपाईंले साइन इन गर्ने सबै डिभाइसहरूमा <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> प्रयोग गर्ने हो?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index a80c288..c91a318 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Annuleren"</string>
<string name="string_continue" msgid="1346732695941131882">"Doorgaan"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Op een andere locatie maken"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Op een andere locatie opslaan"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Een ander apparaat gebruiken"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Opslaan op een ander apparaat"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Een makkelijke manier om beveiligd in te loggen"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik je vingerafdruk, gezichtsvergrendeling of schermvergrendeling om in te loggen met een unieke toegangssleutel die je niet kunt vergeten en die anderen niet kunnen stelen. Meer informatie"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Een locatie kiezen voor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> elke keer gebruiken als je inlogt?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> toegangssleutels"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> toegangssleutels"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Toegangssleutel"</string>
<string name="another_device" msgid="5147276802037801217">"Een ander apparaat"</string>
<string name="other_password_manager" msgid="565790221427004141">"Andere wachtwoordmanagers"</string>
<string name="close_sheet" msgid="1393792015338908262">"Blad sluiten"</string>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 5b401db..838ddfe 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ଆପଣଙ୍କ ସମସ୍ତ ସାଇନ-ଇନ ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index f8f5ba1..74b2ab1 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਬਣਾਓ"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"ਕੋਈ ਹੋਰ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਆਸਾਨ ਤਰੀਕਾ"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"ਵਿਲੱਖਣ ਪਾਸਕੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਾਸਤੇ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰੇ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿਸਨੂੰ ਭੁੱਲਿਆ ਜਾਂ ਚੋਰੀ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਹੋਰ ਜਾਣੋ"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ਲਈ ਕੋਈ ਥਾਂ ਚੁਣੋ"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ਕੀ ਆਪਣੇ ਸਾਰੇ ਸਾਈਨ-ਇਨਾਂ ਲਈ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index e684f23..af6bc9d 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Używać usługi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> w przypadku wszystkich danych logowania?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index 570d0e6..d950bb4 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
<string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index 8a105cd..c46143c 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -11,12 +11,10 @@
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma forma simples de iniciar sessão em segurança"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a sua impressão digital, rosto ou bloqueio de ecrã para iniciar sessão com uma chave de acesso única que não pode ser esquecida nem perdida. Saiba mais"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde quer guardar <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"criar as suas chaves de acesso"</string>
<string name="save_your_password" msgid="6597736507991704307">"guardar a sua palavra-passe"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar as suas informações de início de sessão"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Defina um gestor de palavras-passe predefinido para guardar as suas palavras-passe e chaves de acesso e iniciar sessão mais rapidamente da próxima vez."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Guardar a sua palavra-passe em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Guardar as suas informações de início de sessão em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,15 +22,12 @@
<string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
<string name="password" msgid="6738570945182936667">"palavra-passe"</string>
<string name="sign_ins" msgid="4710739369149469208">"inícios de sessão"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Criar chave de acesso em"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Guardar palavra-passe em"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Guardar início de sessão em"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Criar uma chave de acesso noutro dispositivo?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus inícios de sessão?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este gestor de palavras-passe armazena as suas palavras-passe e chaves de acesso para ajudar a iniciar sessão facilmente."</string>
<string name="set_as_default" msgid="4415328591568654603">"Definir como predefinição"</string>
<string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> palavras-passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index 570d0e6..d950bb4 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Gerenciador de credenciais"</string>
<string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
<string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Criar em outro lugar"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chaves de acesso"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Chave de acesso"</string>
<string name="another_device" msgid="5147276802037801217">"Outro dispositivo"</string>
<string name="other_password_manager" msgid="565790221427004141">"Outros gerenciadores de senha"</string>
<string name="close_sheet" msgid="1393792015338908262">"Fechar página"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index 51955d4..6947281 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Folosești <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pentru toate conectările?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index 2a459c1..dcc643e 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Менеджер учетных данных"</string>
<string name="string_cancel" msgid="6369133483981306063">"Отмена"</string>
<string name="string_continue" msgid="1346732695941131882">"Продолжить"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Создать в другом месте"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Сохранить в другом месте"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Использовать другое устройство"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Сохранить на другом устройстве"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Простой и безопасный способ входа"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"С уникальным ключом доступа, который невозможно украсть или забыть, вы можете подтверждать свою личность по отпечатку пальца, с помощью фейсконтроля или блокировки экрана. Подробнее…"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Выберите, где <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Всегда входить с помощью приложения \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>) и ключи доступа (<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>)"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>)"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключи доступа (<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>)"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ключ доступа"</string>
<string name="another_device" msgid="5147276802037801217">"Другое устройство"</string>
<string name="other_password_manager" msgid="565790221427004141">"Другие менеджеры паролей"</string>
<string name="close_sheet" msgid="1393792015338908262">"Закрыть лист"</string>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index de5a5a2..bf885a9 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"වෙනත් ස්ථානයක තනන්න"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"වෙනත් ස්ථානයකට සුරකින්න"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"වෙනත් උපාංගයක් භාවිතා කරන්න"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"වෙනත් උපාංගයකට සුරකින්න"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"සුරක්ෂිතව පුරනය වීමට සරල ක්රමයක්"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"අමතක කළ නොහැකි හෝ සොරකම් කළ නොහැකි අනන්ය මුරයතුරක් සමග පුරනය වීමට ඔබේ ඇඟිලි සලකුණ, මුහුණ හෝ තිර අගුල භාවිතා කරන්න. තව දැන ගන්න"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> කොතැනක ද යන්න තෝරා ගන්න"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ඔබේ සියලු පුරනය වීම් සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> භාවිතා කරන්න ද?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index 4545868..1c73c57 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -1,24 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Správca prihlasovacích údajov"</string>
<string name="string_cancel" msgid="6369133483981306063">"Zrušiť"</string>
<string name="string_continue" msgid="1346732695941131882">"Pokračovať"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvoriť inde"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Uložiť inde"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Použiť iné zariadenie"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložiť do iného zariadenia"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý spôsob bezpečného prihlasovania"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použite odtlačok prsta, tvár alebo zámku obrazovky a prihláste sa jedinečným prístupovým kľúčom, ktorý sa nedá zabudnúť ani ukradnúť. Ďalšie informácie"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Vyberte, kam <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
- <!-- no translation found for create_your_passkeys (8901224153607590596) -->
- <skip />
+ <string name="create_your_passkeys" msgid="8901224153607590596">"vytvoriť prístupové kľúče"</string>
<string name="save_your_password" msgid="6597736507991704307">"uložiť heslo"</string>
<string name="save_your_sign_in_info" msgid="7213978049817076882">"uložiť prihlasovacie údaje"</string>
- <!-- no translation found for choose_provider_body (8045759834416308059) -->
- <skip />
+ <string name="choose_provider_body" msgid="8045759834416308059">"Nastavte predvoleného správcu hesiel, aby ukladal vaše heslá aj prístupové kľúče, a nabudúce sa prihláste rýchlejšie."</string>
<string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Chcete vytvoriť prístupový kľúč v službe <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_password_title" msgid="8812546498357380545">"Chcete uložiť heslo do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Chcete uložiť svoje prihlasovacie údaje do služby <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -26,22 +22,18 @@
<string name="passkey" msgid="632353688396759522">"prístupový kľúč"</string>
<string name="password" msgid="6738570945182936667">"heslo"</string>
<string name="sign_ins" msgid="4710739369149469208">"prihlasovacie údaje"</string>
- <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
- <skip />
- <!-- no translation found for save_password_to_title (3450480045270186421) -->
- <skip />
- <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
- <skip />
+ <string name="create_passkey_in_title" msgid="2714306562710897785">"Vytvorenie prístupového kľúča v umiestnení"</string>
+ <string name="save_password_to_title" msgid="3450480045270186421">"Uloženie hesla do umiestnenia"</string>
+ <string name="save_sign_in_to_title" msgid="8328143607671760232">"Uloženie prihlasovacích údajov do umiestnenia"</string>
+ <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Chcete vytvoriť prístupový kľúč v inom zariadení?"</string>
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Chcete pre všetky svoje prihlasovacie údaje použiť <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
- <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
- <skip />
+ <string name="use_provider_for_all_description" msgid="6560593199974037820">"Tento správca hesiel uchová vaše heslá a prístupové kľúče, aby vám pomohol ľahšie sa prihlasovať."</string>
<string name="set_as_default" msgid="4415328591568654603">"Nastaviť ako predvolené"</string>
<string name="use_once" msgid="9027366575315399714">"Použiť raz"</string>
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet prístupových kľúčov <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesiel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet prístupových kľúčov: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Prístupový kľúč"</string>
<string name="another_device" msgid="5147276802037801217">"Iné zariadenie"</string>
<string name="other_password_manager" msgid="565790221427004141">"Iní správcovia hesiel"</string>
<string name="close_sheet" msgid="1393792015338908262">"Zavrieť hárok"</string>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 94edf66..969f290 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Upravitelj poverilnic"</string>
<string name="string_cancel" msgid="6369133483981306063">"Prekliči"</string>
<string name="string_continue" msgid="1346732695941131882">"Naprej"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Ustvarjanje na drugem mestu"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite za vse prijave uporabiti »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"Št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ključ za dostop"</string>
<string name="another_device" msgid="5147276802037801217">"Druga naprava"</string>
<string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji gesel"</string>
<string name="close_sheet" msgid="1393792015338908262">"Zapri list"</string>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index 6b85a90..bce0683 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Krijo në një vend tjetër"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Ruaj në një vend tjetër"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Përdor një pajisje tjetër"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Ruaj në një pajisje tjetër"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Një mënyrë e thjeshtë për t\'u identifikuar në mënyrë të sigurt"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Përdor gjurmën e gishtit, fytyrën ose kyçjen e ekranit për t\'u identifikuar me një çelës unik kalimi i cili nuk mund të harrohet ose të vidhet. Mëso më shumë"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Zgjidh se ku të <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Të përdoret <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> për të gjitha identifikimet?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index 79c2eef..6a5235c 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index 7b25056..a4fffb9 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
<string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
<string name="string_continue" msgid="1346732695941131882">"Fortsätt"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Skapa på en annan plats"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Spara på en annan plats"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Använd en annan enhet"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Spara på en annan enhet"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Ett enkelt sätt att logga in säkert på"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Använd ditt fingeravtryck, ansikte eller skärmlås om du vill logga in med en unik nyckel som inte kan glömmas bort eller bli stulen. Läs mer"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Välj var du <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Vill du använda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> för alla dina inloggningar?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -40,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> nycklar"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> nycklar"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Nyckel"</string>
<string name="another_device" msgid="5147276802037801217">"En annan enhet"</string>
<string name="other_password_manager" msgid="565790221427004141">"Andra lösenordshanterare"</string>
<string name="close_sheet" msgid="1393792015338908262">"Stäng kalkylarket"</string>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
new file mode 100644
index 0000000..bfd1074
--- /dev/null
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for app_name (4539824758261855508) -->
+ <skip />
+ <string name="string_cancel" msgid="6369133483981306063">"Ghairi"</string>
+ <string name="string_continue" msgid="1346732695941131882">"Endelea"</string>
+ <string name="string_create_in_another_place" msgid="1033635365843437603">"Unda katika sehemu nyingine"</string>
+ <string name="string_save_to_another_place" msgid="7590325934591079193">"Hifadhi sehemu nyingine"</string>
+ <string name="string_use_another_device" msgid="8754514926121520445">"Tumia kifaa kingine"</string>
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Hifadhi kwenye kifaa kingine"</string>
+ <string name="passkey_creation_intro_title" msgid="402553911484409884">"Njia rahisi ya kuingia katika akaunti kwa usalama"</string>
+ <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Tumia alama ya vidole, uso au kipengele cha kufunga skrini ili uingie katika kaunti kwa kutumia nenosiri la kipekee ambalo haliwezi kusahaulika au kuibiwa. Pata maelezo zaidi"</string>
+ <string name="choose_provider_title" msgid="7245243990139698508">"Chagua mahali pa <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+ <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+ <skip />
+ <string name="save_your_password" msgid="6597736507991704307">"hifadhi nenosiri lako"</string>
+ <string name="save_your_sign_in_info" msgid="7213978049817076882">"hifadhi maelezo yako ya kuingia katika akaunti"</string>
+ <!-- no translation found for choose_provider_body (8045759834416308059) -->
+ <skip />
+ <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ungependa kuunda ufunguo wa siri katika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_password_title" msgid="8812546498357380545">"Ungependa kuhifadhi nenosiri lako kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Ungependa kuhifadhi maelezo yako ya kuingia katika akaunti kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
+ <string name="choose_create_option_description" msgid="4419171903963100257">"Unaweza kutumia <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> yako ya <xliff:g id="TYPE">%2$s</xliff:g> kwenye kifaa chochote. Imehifadhiwa kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> kwa ajili ya <xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g>"</string>
+ <string name="passkey" msgid="632353688396759522">"ufunguo wa siri"</string>
+ <string name="password" msgid="6738570945182936667">"nenosiri"</string>
+ <string name="sign_ins" msgid="4710739369149469208">"michakato ya kuingia katika akaunti"</string>
+ <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
+ <skip />
+ <!-- no translation found for save_password_to_title (3450480045270186421) -->
+ <skip />
+ <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
+ <skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
+ <string name="use_provider_for_all_title" msgid="4201020195058980757">"Ungependa kutumia <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kwa ajili ya michakato yako yote ya kuingia katika akaunti?"</string>
+ <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
+ <skip />
+ <string name="set_as_default" msgid="4415328591568654603">"Weka iwe chaguomsingi"</string>
+ <string name="use_once" msgid="9027366575315399714">"Tumia mara moja"</string>
+ <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, funguo <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> za siri"</string>
+ <string name="more_options_usage_passwords" msgid="1632047277723187813">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
+ <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Funguo <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> za siri"</string>
+ <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
+ <skip />
+ <string name="another_device" msgid="5147276802037801217">"Kifaa kingine"</string>
+ <string name="other_password_manager" msgid="565790221427004141">"Vidhibiti vinginevyo vya manenosiri"</string>
+ <string name="close_sheet" msgid="1393792015338908262">"Funga laha"</string>
+ <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"Rudi kwenye ukurasa uliotangulia"</string>
+ <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"Ungependa kutumia ufunguo wa siri uliohifadhiwa wa<xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"Ungependa kutumia kitambulisho kilichohifadhiwa cha kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"Chagua vitambulisho vilivyohifadhiwa kwa ajili ya kuingia katika akaunti ya <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"Ingia katika akaunti kwa kutumia njia nyingine"</string>
+ <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"Hapana"</string>
+ <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"Endelea"</string>
+ <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"Chaguo za kuingia katika akaunti"</string>
+ <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"Kwa ajili ya <xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+ <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"Vidhibiti vya manenosiri vilivyofungwa"</string>
+ <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"Gusa ili ufungue"</string>
+ <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Dhibiti michakato ya kuingia katika akaunti"</string>
+ <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Kutoka kwenye kifaa kingine"</string>
+ <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Tumia kifaa tofauti"</string>
+</resources>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 646c469..10c5259 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"உங்கள் அனைத்து உள்நுழைவுகளுக்கும் <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index d94f3d3..f7617b3 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"మీ అన్ని సైన్-ఇన్ వివరాల కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ను ఉపయోగించాలా?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index 43f3f0f..d70e94a 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"เครื่องมือจัดการข้อมูลเข้าสู่ระบบ"</string>
<string name="string_cancel" msgid="6369133483981306063">"ยกเลิก"</string>
<string name="string_continue" msgid="1346732695941131882">"ต่อไป"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"สร้างในตำแหน่งอื่น"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"ใช้ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> สำหรับการลงชื่อเข้าใช้ทั้งหมดใช่ไหม"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> รายการ"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> รายการ"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"พาสคีย์"</string>
<string name="another_device" msgid="5147276802037801217">"อุปกรณ์อื่น"</string>
<string name="other_password_manager" msgid="565790221427004141">"เครื่องมือจัดการรหัสผ่านอื่นๆ"</string>
<string name="close_sheet" msgid="1393792015338908262">"ปิดชีต"</string>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 4dae037..01fd2f0 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"Manager ng Kredensyal"</string>
<string name="string_cancel" msgid="6369133483981306063">"Kanselahin"</string>
<string name="string_continue" msgid="1346732695941131882">"Magpatuloy"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"Gumawa sa ibang lugar"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Gamitin ang <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para sa lahat ng iyong pag-sign in?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> (na) passkey"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> (na) passkey"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"Passkey"</string>
<string name="another_device" msgid="5147276802037801217">"Ibang device"</string>
<string name="other_password_manager" msgid="565790221427004141">"Iba pang password manager"</string>
<string name="close_sheet" msgid="1393792015338908262">"Isara ang sheet"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index c1ccd98..30ed43e 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -8,7 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Başka bir yerde oluşturun"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Başka bir yere kaydedin"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Başka bir cihaz kullan"</string>
- <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydedin"</string>
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydet"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Güvenli bir şekilde oturum açmanın basit yolu"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Parmak iziniz, yüzünüz ya da ekran kilidinizi kullanarak unutması veya çalınması mümkün olmayan benzersiz bir şifre anahtarıyla oturum açın. Daha fazla bilgi"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> yerini seçin"</string>
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Tüm oturum açma işlemlerinizde <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kullanılsın mı?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 396da4d..69d4612 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -8,8 +8,7 @@
<string name="string_create_in_another_place" msgid="1033635365843437603">"Створити в іншому місці"</string>
<string name="string_save_to_another_place" msgid="7590325934591079193">"Зберегти в іншому місці"</string>
<string name="string_use_another_device" msgid="8754514926121520445">"Скористатись іншим пристроєм"</string>
- <!-- no translation found for string_save_to_another_device (1959562542075194458) -->
- <skip />
+ <string name="string_save_to_another_device" msgid="1959562542075194458">"Зберегти на іншому пристрої"</string>
<string name="passkey_creation_intro_title" msgid="402553911484409884">"Зручний спосіб для безпечного входу"</string>
<string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користуйтеся відбитком пальця, фейсконтролем або іншим способом розблокування екрана, щоб входити в обліковий запис за допомогою унікального ключа доступу, який неможливо забути чи викрасти. Докладніше"</string>
<string name="choose_provider_title" msgid="7245243990139698508">"Виберіть, де <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
@@ -32,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Використовувати сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> в усіх випадках входу?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index e67b94c..2d66079 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"سند سے متعلق مینیجر"</string>
<string name="string_cancel" msgid="6369133483981306063">"منسوخ کریں"</string>
<string name="string_continue" msgid="1346732695941131882">"جاری رکھیں"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"دوسرے مقام میں تخلیق کریں"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"اپنے سبھی سائن انز کے لیے <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> کا استعمال کریں؟"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> پاس کیز"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> پاس کیز"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"پاس کی"</string>
<string name="another_device" msgid="5147276802037801217">"دوسرا آلہ"</string>
<string name="other_password_manager" msgid="565790221427004141">"دیگر پاس ورڈ مینیجرز"</string>
<string name="close_sheet" msgid="1393792015338908262">"شیٹ بند کریں"</string>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index 6c3e211..4ac35b2 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Hamma kirishlarda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ishlatilsinmi?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index d4703f3..fd5b986 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Dùng <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cho mọi thông tin đăng nhập của bạn?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index 145eac2..a14dd2f 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index f277c22..71dfa1a 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
<string name="string_cancel" msgid="6369133483981306063">"取消"</string>
<string name="string_continue" msgid="1346732695941131882">"繼續"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資料嗎?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密鑰"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密鑰"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
<string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
<string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index fd1dfc8e..0d636c2 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- no translation found for app_name (4539824758261855508) -->
- <skip />
+ <string name="app_name" msgid="4539824758261855508">"憑證管理工具"</string>
<string name="string_cancel" msgid="6369133483981306063">"取消"</string>
<string name="string_continue" msgid="1346732695941131882">"繼續"</string>
<string name="string_create_in_another_place" msgid="1033635365843437603">"在其他位置建立"</string>
@@ -31,6 +30,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資訊嗎?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
@@ -39,8 +40,7 @@
<string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密碼金鑰"</string>
<string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
<string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密碼金鑰"</string>
- <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
- <skip />
+ <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
<string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
<string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
<string name="close_sheet" msgid="1393792015338908262">"關閉功能表"</string>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index fd2b83e..a35c6d2 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -31,6 +31,8 @@
<skip />
<!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
<skip />
+ <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
+ <skip />
<string name="use_provider_for_all_title" msgid="4201020195058980757">"Sebenzisa i-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kukho konke ukungena kwakho ngemvume?"</string>
<!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
<skip />
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 8c9023c..870d983 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -18,9 +18,13 @@
<!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
<string name="string_save_to_another_device">Save to another device</string>
<!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
- <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
- <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+ <string name="passkey_creation_intro_title">Safer with passkeys</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
+ <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
+ <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+ <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
+ <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
<!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 1db1d1c..2780c3c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -40,12 +40,9 @@
import android.os.Bundle
import android.os.ResultReceiver
import android.service.credentials.CredentialProviderService
-import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RemoteInfo
-import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -146,20 +143,30 @@
providerDisabledList, context)
var defaultProvider: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
+ var createOptionSize = 0
+ var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {defaultProvider = providerInfo}
if (providerInfo.remoteEntry != null) {
remoteEntry = providerInfo.remoteEntry!!
}
+ if (providerInfo.createOptions.isNotEmpty()) {
+ createOptionSize += providerInfo.createOptions.size
+ lastSeenProviderWithNonEmptyCreateOptions = providerInfo
+ }
}
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
- toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
+ CreateFlowUtils.toCreateScreenState(
+ createOptionSize, false,
+ requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
- toActiveEntry(defaultProvider, remoteEntry),
+ CreateFlowUtils.toActiveEntry(
+ /*defaultProvider=*/defaultProvider, createOptionSize,
+ lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
)
}
@@ -194,6 +201,7 @@
.setRemoteEntry(
newRemoteEntry("key2", "subkey-1")
)
+ .setIsDefaultProvider(true)
.build(),
CreateCredentialProviderData
.Builder("com.dashlane")
@@ -517,38 +525,4 @@
"tribank.us"
)
}
-
- private fun toCreateScreenState(
- requestDisplayInfo: RequestDisplayInfo,
- defaultProvider: EnabledProviderInfo?,
- remoteEntry: RemoteInfo?,
- ): CreateScreenState {
- return if (
- defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
- ){
- CreateScreenState.EXTERNAL_ONLY_SELECTION
- } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
- CreateScreenState.PASSKEY_INTRO
- } else {
- CreateScreenState.PROVIDER_SELECTION
- }
- } else {
- CreateScreenState.CREATION_OPTION_SELECTION
- }
- }
-
- private fun toActiveEntry(
- defaultProvider: EnabledProviderInfo?,
- remoteEntry: RemoteInfo?,
- ): ActiveEntry? {
- return if (
- defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
- ) {
- ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
- } else if (
- defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
- ActiveEntry(defaultProvider, remoteEntry)
- } else null
- }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index d324f87..6a4c599 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -69,6 +69,7 @@
)
providerActivityResult.value?.let {
viewModel.onProviderActivityResult(it)
+ providerActivityResult.value = null
}
CreateCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
}
@@ -80,6 +81,7 @@
)
providerActivityResult.value?.let {
viewModel.onProviderActivityResult(it)
+ providerActivityResult.value = null
}
GetCredentialScreen(viewModel = viewModel, providerActivityLauncher = launcher)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 357c55d..0d7e819 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -28,6 +28,9 @@
import com.android.credentialmanager.createflow.CreateOptionInfo
import com.android.credentialmanager.createflow.RemoteInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ActiveEntry
import com.android.credentialmanager.getflow.ActionEntryInfo
import com.android.credentialmanager.getflow.AuthenticationEntryInfo
import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -36,6 +39,7 @@
import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
import com.android.credentialmanager.jetpack.provider.ActionUi
import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
import com.android.credentialmanager.jetpack.provider.SaveEntryUi
@@ -243,7 +247,8 @@
createCredentialRequestJetpack.password,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_password)!!
+ context.getDrawable(R.drawable.ic_password)!!,
+ requestInfo.isFirstUsage
)
}
is CreatePublicKeyCredentialRequest -> {
@@ -261,7 +266,8 @@
displayName,
createCredentialRequestJetpack.type,
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_passkey)!!)
+ context.getDrawable(R.drawable.ic_passkey)!!,
+ requestInfo.isFirstUsage)
}
// TODO: correctly parsing for other sign-ins
else -> {
@@ -270,11 +276,58 @@
"Elisa Beckett",
"other-sign-ins",
requestInfo.appPackageName,
- context.getDrawable(R.drawable.ic_other_sign_in)!!)
+ context.getDrawable(R.drawable.ic_other_sign_in)!!,
+ requestInfo.isFirstUsage)
}
}
}
+ fun toCreateScreenState(
+ createOptionSize: Int,
+ isOnPasskeyIntroStateAlready: Boolean,
+ requestDisplayInfo: RequestDisplayInfo,
+ defaultProvider: EnabledProviderInfo?,
+ remoteEntry: RemoteInfo?,
+ ): CreateScreenState {
+ return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+ .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
+ CreateScreenState.PASSKEY_INTRO
+ } else if (
+ (defaultProvider == null || defaultProvider.createOptions.isEmpty()
+ ) && createOptionSize > 1) {
+ CreateScreenState.PROVIDER_SELECTION
+ } else if (
+ ((defaultProvider == null || defaultProvider.createOptions.isEmpty()
+ ) && createOptionSize == 1) || (
+ defaultProvider != null && defaultProvider.createOptions.isNotEmpty())) {
+ CreateScreenState.CREATION_OPTION_SELECTION
+ } else if (createOptionSize == 0 && remoteEntry != null) {
+ CreateScreenState.EXTERNAL_ONLY_SELECTION
+ } else {
+ // TODO: properly handle error and gracefully finish itself
+ throw java.lang.IllegalStateException("Empty provider list.")
+ }
+ }
+
+ fun toActiveEntry(
+ defaultProvider: EnabledProviderInfo?,
+ createOptionSize: Int,
+ lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+ remoteEntry: RemoteInfo?,
+ ): ActiveEntry? {
+ return if (
+ defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+ ActiveEntry(defaultProvider, remoteEntry)
+ } else if (
+ defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+ ) {
+ ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+ } else if (createOptionSize == 1) {
+ ActiveEntry(lastSeenProviderWithNonEmptyCreateOptions!!,
+ lastSeenProviderWithNonEmptyCreateOptions.createOptions.first())
+ } else null
+ }
+
private fun toCreationOptionInfoList(
providerId: String,
creationEntries: List<Entry>,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index d8dd1a7..5d22bfd 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -60,12 +60,6 @@
viewModel: CreateCredentialViewModel,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val selectEntryCallback: (EntryInfo) -> Unit = {
- viewModel.onEntrySelected(it, providerActivityLauncher)
- }
- val confirmEntryCallback: () -> Unit = {
- viewModel.onConfirmEntrySelected(providerActivityLauncher)
- }
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
skipHalfExpanded = true
@@ -75,53 +69,57 @@
sheetState = state,
sheetContent = {
val uiState = viewModel.uiState
- when (uiState.currentScreenState) {
- CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
- onConfirm = viewModel::onConfirmIntro,
- onCancel = viewModel::onCancel,
- )
- CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- enabledProviderList = uiState.enabledProviders,
- disabledProviderList = uiState.disabledProviders,
- onCancel = viewModel::onCancel,
- onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
- onDisabledPasswordManagerSelected =
+ if (!uiState.hidden) {
+ when (uiState.currentScreenState) {
+ CreateScreenState.PASSKEY_INTRO -> ConfirmationCard(
+ onConfirm = viewModel::onConfirmIntro,
+ onCancel = viewModel::onCancel,
+ )
+ CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ disabledProviderList = uiState.disabledProviders,
+ onCancel = viewModel::onCancel,
+ onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
+ onDisabledPasswordManagerSelected =
viewModel::onDisabledPasswordManagerSelected,
- onRemoteEntrySelected = selectEntryCallback,
- )
- CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- enabledProviderList = uiState.enabledProviders,
- providerInfo = uiState.activeEntry?.activeProvider!!,
- createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
- showActiveEntryOnly = uiState.showActiveEntryOnly,
- onOptionSelected = selectEntryCallback,
- onConfirm = confirmEntryCallback,
- onCancel = viewModel::onCancel,
- onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
- )
- CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- enabledProviderList = uiState.enabledProviders,
- disabledProviderList = uiState.disabledProviders,
- onBackButtonSelected = viewModel::onBackButtonSelected,
- onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
- onDisabledPasswordManagerSelected =
+ onRemoteEntrySelected = viewModel::onEntrySelected,
+ )
+ CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
+ showActiveEntryOnly = uiState.showActiveEntryOnly,
+ onOptionSelected = viewModel::onEntrySelected,
+ onConfirm = viewModel::onConfirmEntrySelected,
+ onCancel = viewModel::onCancel,
+ onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
+ )
+ CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ enabledProviderList = uiState.enabledProviders,
+ disabledProviderList = uiState.disabledProviders,
+ onBackButtonSelected = viewModel::onBackButtonSelected,
+ onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
+ onDisabledPasswordManagerSelected =
viewModel::onDisabledPasswordManagerSelected,
- onRemoteEntrySelected = selectEntryCallback,
- )
- CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
- providerInfo = uiState.activeEntry?.activeProvider!!,
- onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
- )
- CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
- onOptionSelected = selectEntryCallback,
- onConfirm = confirmEntryCallback,
- onCancel = viewModel::onCancel,
- )
+ onRemoteEntrySelected = viewModel::onEntrySelected,
+ )
+ CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
+ providerInfo = uiState.activeEntry?.activeProvider!!,
+ onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
+ )
+ CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = viewModel::onEntrySelected,
+ onConfirm = viewModel::onConfirmEntrySelected,
+ onCancel = viewModel::onCancel,
+ )
+ }
+ } else if (uiState.hidden && uiState.selectedEntry != null) {
+ viewModel.launchProviderUi(providerActivityLauncher)
}
},
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
@@ -142,12 +140,11 @@
) {
ContainerCard() {
Column() {
- Icon(
- painter = painterResource(R.drawable.ic_passkey),
+ Image(
+ painter = painterResource(R.drawable.ic_passkeys_onboarding),
contentDescription = null,
- tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
- .padding(top = 24.dp, bottom = 12.dp)
+ .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
)
TextOnSurface(
text = stringResource(R.string.passkey_creation_intro_title),
@@ -161,11 +158,62 @@
thickness = 16.dp,
color = Color.Transparent
)
- TextSecondary(
- text = stringResource(R.string.passkey_creation_intro_body),
- style = MaterialTheme.typography.bodyLarge,
- modifier = Modifier.padding(horizontal = 28.dp),
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(R.drawable.ic_passkeys_onboarding_password),
+ contentDescription = null
+ )
+ TextSecondary(
+ text = stringResource(R.string.passkey_creation_intro_body_password),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent
)
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
+ contentDescription = null
+ )
+ TextSecondary(
+ text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
+ Divider(
+ thickness = 16.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(R.drawable.ic_passkeys_onboarding_device),
+ contentDescription = null
+ )
+ TextSecondary(
+ text = stringResource(R.string.passkey_creation_intro_body_device),
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
Divider(
thickness = 32.dp,
color = Color.Transparent
@@ -270,7 +318,7 @@
MoreOptionsDisabledProvidersRow(
disabledProviders = disabledProviderList,
onDisabledPasswordManagerSelected =
- onDisabledPasswordManagerSelected,
+ onDisabledPasswordManagerSelected,
)
}
}
@@ -700,7 +748,7 @@
},
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
- modifier = Modifier.padding(start = 18.dp).size(32.dp)
+ modifier = Modifier.padding(horizontal = 18.dp).size(32.dp)
)
},
label = {
@@ -762,7 +810,7 @@
onClick = onOptionSelected,
icon = {
Image(
- modifier = Modifier.size(32.dp).padding(start = 16.dp),
+ modifier = Modifier.padding(horizontal = 16.dp).size(32.dp),
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null
)
@@ -772,17 +820,17 @@
TextOnSurfaceVariant(
text = providerInfo.displayName,
style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+ modifier = Modifier.padding(top = 16.dp),
)
if (createOptionInfo.userProviderDisplayName != null) {
TextSecondary(
text = createOptionInfo.userProviderDisplayName,
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(start = 16.dp),
)
}
if (createOptionInfo.passwordCount != null &&
- createOptionInfo.passkeyCount != null) {
+ createOptionInfo.passkeyCount != null
+ ) {
TextSecondary(
text =
stringResource(
@@ -791,7 +839,7 @@
createOptionInfo.passkeyCount
),
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+ modifier = Modifier.padding(bottom = 16.dp),
)
} else if (createOptionInfo.passwordCount != null) {
TextSecondary(
@@ -801,7 +849,7 @@
createOptionInfo.passwordCount
),
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+ modifier = Modifier.padding(bottom = 16.dp),
)
} else if (createOptionInfo.passkeyCount != null) {
TextSecondary(
@@ -811,7 +859,7 @@
createOptionInfo.passkeyCount
),
style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+ modifier = Modifier.padding(bottom = 16.dp),
)
} else if (createOptionInfo.totalCredentialCount != null) {
// TODO: Handle the case when there is total count
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 393cf7d..3d23f5d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.createflow
+import android.app.Activity
import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -26,6 +27,7 @@
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CreateFlowUtils
import com.android.credentialmanager.CredentialManagerRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ProviderActivityResult
@@ -39,6 +41,7 @@
val showActiveEntryOnly: Boolean,
val activeEntry: ActiveEntry? = null,
val selectedEntry: EntryInfo? = null,
+ val hidden: Boolean = false,
)
class CreateCredentialViewModel(
@@ -58,24 +61,26 @@
fun onConfirmIntro() {
var createOptionSize = 0
+ var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+ var remoteEntry: RemoteInfo? = null
uiState.enabledProviders.forEach {
- enabledProvider -> createOptionSize += enabledProvider.createOptions.size}
- uiState = if (createOptionSize > 1) {
- uiState.copy(
- currentScreenState = CreateScreenState.PROVIDER_SELECTION,
- showActiveEntryOnly = true
- )
- } else if (createOptionSize == 1){
- uiState.copy(
- currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
- showActiveEntryOnly = false,
- activeEntry = ActiveEntry(uiState.enabledProviders.first(),
- uiState.enabledProviders.first().createOptions.first()
- )
- )
- } else {
- throw java.lang.IllegalStateException("Empty provider list.")
+ enabledProvider ->
+ if (enabledProvider.createOptions.isNotEmpty()) {
+ createOptionSize += enabledProvider.createOptions.size
+ lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+ }
+ if (enabledProvider.remoteEntry != null) {
+ remoteEntry = enabledProvider.remoteEntry!!
+ }
}
+ uiState = uiState.copy(
+ currentScreenState = CreateFlowUtils.toCreateScreenState(
+ createOptionSize, true,
+ uiState.requestDisplayInfo, null, remoteEntry),
+ showActiveEntryOnly = createOptionSize > 1,
+ activeEntry = CreateFlowUtils.toActiveEntry(
+ null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+ )
}
fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
@@ -128,10 +133,7 @@
// TODO: implement the if choose as default or not logic later
}
- fun onEntrySelected(
- selectedEntry: EntryInfo,
- launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
- ) {
+ fun onEntrySelected(selectedEntry: EntryInfo) {
val providerId = selectedEntry.providerId
val entryKey = selectedEntry.entryKey
val entrySubkey = selectedEntry.entrySubkey
@@ -139,10 +141,10 @@
"Account Selector", "Option selected for entry: " +
" {provider=$providerId, key=$entryKey, subkey=$entrySubkey")
if (selectedEntry.pendingIntent != null) {
- uiState = uiState.copy(selectedEntry = selectedEntry)
- val intentSenderRequest = IntentSenderRequest.Builder(selectedEntry.pendingIntent)
- .setFillInIntent(selectedEntry.fillInIntent).build()
- launcher.launch(intentSenderRequest)
+ uiState = uiState.copy(
+ selectedEntry = selectedEntry,
+ hidden = true,
+ )
} else {
CredentialManagerRepo.getInstance().onOptionSelected(
providerId,
@@ -155,12 +157,23 @@
}
}
- fun onConfirmEntrySelected(
+ fun launchProviderUi(
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
+ val entry = uiState.selectedEntry
+ if (entry != null && entry.pendingIntent != null) {
+ val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+ .setFillInIntent(entry.fillInIntent).build()
+ launcher.launch(intentSenderRequest)
+ } else {
+ Log.w("Account Selector", "No provider UI to launch")
+ }
+ }
+
+ fun onConfirmEntrySelected() {
val selectedEntry = uiState.activeEntry?.activeEntryInfo
if (selectedEntry != null) {
- onEntrySelected(selectedEntry, launcher)
+ onEntrySelected(selectedEntry)
} else {
Log.w("Account Selector",
"Illegal state: confirm is pressed but activeEntry isn't set.")
@@ -174,21 +187,29 @@
val entry = uiState.selectedEntry
val resultCode = providerActivityResult.resultCode
val resultData = providerActivityResult.data
- if (entry != null) {
- val providerId = entry.providerId
- Log.d("Account Selector", "Got provider activity result: {provider=" +
- "$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
- "resultCode=$resultCode, resultData=$resultData}"
- )
- CredentialManagerRepo.getInstance().onOptionSelected(
- providerId, entry.entryKey, entry.entrySubkey, resultCode, resultData,
+ if (resultCode == Activity.RESULT_CANCELED) {
+ // Re-display the CredMan UI if the user canceled from the provider UI.
+ uiState = uiState.copy(
+ selectedEntry = null,
+ hidden = false,
)
} else {
- Log.w("Account Selector",
- "Illegal state: received a provider result but found no matching entry.")
+ if (entry != null) {
+ val providerId = entry.providerId
+ Log.d("Account Selector", "Got provider activity result: {provider=" +
+ "$providerId, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
+ "resultCode=$resultCode, resultData=$resultData}"
+ )
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ providerId, entry.entryKey, entry.entrySubkey, resultCode, resultData,
+ )
+ } else {
+ Log.w("Account Selector",
+ "Illegal state: received a provider result but found no matching entry.")
+ }
+ dialogResult.value = DialogResult(
+ ResultState.COMPLETE,
+ )
}
- dialogResult.value = DialogResult(
- ResultState.COMPLETE,
- )
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 9ac524a..21abe08 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -77,6 +77,7 @@
val type: String,
val appDomainName: String,
val typeIcon: Drawable,
+ val isFirstUsage: Boolean,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 31dc069..21342a1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -71,9 +71,6 @@
viewModel: GetCredentialViewModel,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- val entrySelectionCallback: (EntryInfo) -> Unit = {
- viewModel.onEntrySelected(it, providerActivityLauncher)
- }
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
skipHalfExpanded = true
@@ -84,20 +81,24 @@
sheetState = state,
sheetContent = {
val uiState = viewModel.uiState
- when (uiState.currentScreenState) {
- GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
- requestDisplayInfo = uiState.requestDisplayInfo,
- providerDisplayInfo = uiState.providerDisplayInfo,
- onEntrySelected = entrySelectionCallback,
- onCancel = viewModel::onCancel,
- onMoreOptionSelected = viewModel::onMoreOptionSelected,
- )
- GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
- providerInfoList = uiState.providerInfoList,
- providerDisplayInfo = uiState.providerDisplayInfo,
- onEntrySelected = entrySelectionCallback,
- onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
- )
+ if (!uiState.hidden) {
+ when (uiState.currentScreenState) {
+ GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onCancel = viewModel::onCancel,
+ onMoreOptionSelected = viewModel::onMoreOptionSelected,
+ )
+ GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
+ providerInfoList = uiState.providerInfoList,
+ providerDisplayInfo = uiState.providerDisplayInfo,
+ onEntrySelected = viewModel::onEntrySelected,
+ onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+ )
+ }
+ } else if (uiState.hidden && uiState.selectedEntry != null) {
+ viewModel.launchProviderUi(providerActivityLauncher)
}
},
scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 6dea9c2..33e7021 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.getflow
+import android.app.Activity
import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
@@ -39,6 +40,7 @@
val requestDisplayInfo: RequestDisplayInfo,
val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
val selectedEntry: EntryInfo? = null,
+ val hidden: Boolean = false,
)
class GetCredentialViewModel(
@@ -56,17 +58,14 @@
return dialogResult
}
- fun onEntrySelected(
- entry: EntryInfo,
- launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
- ) {
+ fun onEntrySelected(entry: EntryInfo) {
Log.d("Account Selector", "credential selected:" +
" {provider=${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}}")
if (entry.pendingIntent != null) {
- uiState = uiState.copy(selectedEntry = entry)
- val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
- .setFillInIntent(entry.fillInIntent).build()
- launcher.launch(intentSenderRequest)
+ uiState = uiState.copy(
+ selectedEntry = entry,
+ hidden = true,
+ )
} else {
CredentialManagerRepo.getInstance().onOptionSelected(
entry.providerId, entry.entryKey, entry.entrySubkey,
@@ -75,24 +74,45 @@
}
}
+ fun launchProviderUi(
+ launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
+ ) {
+ val entry = uiState.selectedEntry
+ if (entry != null && entry.pendingIntent != null) {
+ val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
+ .setFillInIntent(entry.fillInIntent).build()
+ launcher.launch(intentSenderRequest)
+ } else {
+ Log.w("Account Selector", "No provider UI to launch")
+ }
+ }
+
fun onProviderActivityResult(providerActivityResult: ProviderActivityResult) {
val entry = uiState.selectedEntry
val resultCode = providerActivityResult.resultCode
val resultData = providerActivityResult.data
- if (entry != null) {
- Log.d("Account Selector", "Got provider activity result: {provider=" +
- "${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
- "resultCode=$resultCode, resultData=$resultData}"
- )
- CredentialManagerRepo.getInstance().onOptionSelected(
- entry.providerId, entry.entryKey, entry.entrySubkey,
- resultCode, resultData,
+ if (resultCode == Activity.RESULT_CANCELED) {
+ // Re-display the CredMan UI if the user canceled from the provider UI.
+ uiState = uiState.copy(
+ selectedEntry = null,
+ hidden = false,
)
} else {
- Log.w("Account Selector",
- "Illegal state: received a provider result but found no matching entry.")
+ if (entry != null) {
+ Log.d("Account Selector", "Got provider activity result: {provider=" +
+ "${entry.providerId}, key=${entry.entryKey}, subkey=${entry.entrySubkey}, " +
+ "resultCode=$resultCode, resultData=$resultData}"
+ )
+ CredentialManagerRepo.getInstance().onOptionSelected(
+ entry.providerId, entry.entryKey, entry.entrySubkey,
+ resultCode, resultData,
+ )
+ } else {
+ Log.w("Account Selector",
+ "Illegal state: received a provider result but found no matching entry.")
+ }
+ dialogResult.value = DialogResult(ResultState.COMPLETE)
}
- dialogResult.value = DialogResult(ResultState.COMPLETE)
}
fun onMoreOptionSelected() {
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index dd29827..372a276 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -25,5 +25,6 @@
"//apex_available:platform",
"com.android.adservices",
"com.android.cellbroadcast",
+ "com.android.healthconnect",
],
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index ddf66aa..44f0343 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -26,9 +26,9 @@
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
-import com.android.settingslib.spa.widget.Illustration
-import com.android.settingslib.spa.widget.IllustrationModel
-import com.android.settingslib.spa.widget.ResourceType
+import com.android.settingslib.spa.widget.illustration.Illustration
+import com.android.settingslib.spa.widget.illustration.IllustrationModel
+import com.android.settingslib.spa.widget.illustration.ResourceType
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index b627a70..1c5a1ce 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -33,4 +33,3 @@
include ':spa'
include ':gallery'
include ':testutils'
-include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index b1d8d0d..19963fb 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -27,6 +27,8 @@
defaultConfig {
minSdk MIN_SDK
targetSdk TARGET_SDK
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
sourceSets {
@@ -37,6 +39,13 @@
res.srcDirs = ["res"]
manifest.srcFile "AndroidManifest.xml"
}
+ androidTest {
+ kotlin {
+ srcDir "../tests/src"
+ }
+ res.srcDirs = ["../tests/res"]
+ manifest.srcFile "../tests/AndroidManifest.xml"
+ }
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@@ -52,6 +61,11 @@
composeOptions {
kotlinCompilerExtensionVersion jetpack_compose_compiler_version
}
+ buildTypes {
+ debug {
+ testCoverageEnabled = true
+ }
+ }
}
dependencies {
@@ -72,4 +86,29 @@
api "com.google.android.material:material:1.7.0-alpha03"
debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
implementation "com.airbnb.android:lottie-compose:5.2.0"
+
+ androidTestImplementation project(":testutils")
+ androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
+}
+
+task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
+ group = "Reporting"
+ description = "Generate Jacoco coverage reports after running tests."
+
+ sourceDirectories.from = files("src")
+ classDirectories.from = fileTree(
+ dir: "$buildDir/tmp/kotlin-classes/debug",
+ excludes: [
+ "com/android/settingslib/spa/debug/**",
+
+ // Excludes files forked from AndroidX.
+ "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+ "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+ // Excludes files forked from Accompanist.
+ "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+ "com/android/settingslib/spa/framework/compose/Pager*",
+ ],
+ )
+ executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
index ae325f8..e3e1220 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -20,6 +20,7 @@
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
+import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
@@ -117,13 +118,17 @@
return true
}
- override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
- drawable.setLayoutDirection(
- when (layoutDirection) {
- LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
- LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
- }
- )
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+ }
+ return false
+ }
override val intrinsicSize: Size get() = drawableIntrinsicSize
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
deleted file mode 100644
index dbf8836..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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.framework.compose
-
-import android.annotation.SuppressLint
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * *************************************************************************************************
- * This file was forked from AndroidX:
- * lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
- * TODO: Replace with AndroidX when it's usable.
- */
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
- *
- * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
- * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
- * falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
- lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = this.value,
- lifecycle = lifecycleOwner.lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycle] is at least [minActiveState].
- *
- * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
- lifecycle: Lifecycle,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = this.value,
- lifecycle = lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
- * at least [minActiveState].
- *
- * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
- * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
- * [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
- initialValue: T,
- lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = initialValue,
- lifecycle = lifecycleOwner.lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycle] is at
- * least [minActiveState].
- *
- * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
- initialValue: T,
- lifecycle: Lifecycle,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> {
- return produceState(initialValue, this, lifecycle, minActiveState, context) {
- lifecycle.repeatOnLifecycle(minActiveState) {
- if (context == EmptyCoroutineContext) {
- this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
- } else withContext(context) {
- this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
- }
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
index 8d0313f..3f7cc19 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Keyboards.kt
@@ -18,7 +18,6 @@
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
@@ -32,7 +31,7 @@
*/
@OptIn(ExperimentalComposeUiApi::class)
@Composable
-fun hideKeyboardAction(): KeyboardActionScope.() -> Unit {
+fun hideKeyboardAction(): () -> Unit {
val keyboardController = LocalSoftwareKeyboardController.current
return { keyboardController?.hide() }
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
index 4df7794..392089a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -19,8 +19,6 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -36,7 +34,6 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -123,7 +120,7 @@
contentPadding: PaddingValues = PaddingValues(0.dp),
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
key: ((page: Int) -> Any)? = null,
- content: @Composable() (PagerScope.(page: Int) -> Unit),
+ content: @Composable PagerScope.(page: Int) -> Unit,
) {
Pager(
count = count,
@@ -175,24 +172,8 @@
.collect { state.updateCurrentPageBasedOnLazyListState() }
}
val density = LocalDensity.current
- val layoutDirection = LocalLayoutDirection.current
- LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) {
- with(density) {
- // this should be exposed on LazyListLayoutInfo instead. b/200920410
- state.afterContentPadding = if (isVertical) {
- if (!reverseLayout) {
- contentPadding.calculateBottomPadding()
- } else {
- contentPadding.calculateTopPadding()
- }
- } else {
- if (!reverseLayout) {
- contentPadding.calculateEndPadding(layoutDirection)
- } else {
- contentPadding.calculateStartPadding(layoutDirection)
- }
- }.roundToPx()
- }
+ LaunchedEffect(density, state, itemSpacing) {
+ with(density) { state.itemSpacing = itemSpacing.roundToPx() }
}
val pagerScope = remember(state) { PagerScopeImpl(state) }
@@ -203,6 +184,7 @@
ConsumeFlingNestedScrollConnection(
consumeHorizontal = !isVertical,
consumeVertical = isVertical,
+ pagerState = state,
)
}
@@ -268,6 +250,7 @@
private class ConsumeFlingNestedScrollConnection(
private val consumeHorizontal: Boolean,
private val consumeVertical: Boolean,
+ private val pagerState: PagerState,
) : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
@@ -281,9 +264,15 @@
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- // We can consume all post fling velocity on the main-axis
- // so that it doesn't propagate up to the Pager
- return available.consume(consumeHorizontal, consumeVertical)
+ return if (pagerState.currentPageOffset != 0f) {
+ // The Pager is already scrolling. This means that a nested scroll child was
+ // scrolled to end, and the Pager can use this fling
+ Velocity.Zero
+ } else {
+ // A nested scroll child is still scrolling. We can consume all post fling
+ // velocity on the main-axis so that it doesn't propagate up to the Pager
+ available.consume(consumeHorizontal, consumeVertical)
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
index 21ba117..480335d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
@@ -85,12 +85,14 @@
return layoutInfo.visibleItemsInfo.maxByOrNull {
val start = maxOf(it.offset, 0)
val end = minOf(
- it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding)
+ it.offset + it.size,
+ layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding
+ )
end - start
}
}
- internal var afterContentPadding = 0
+ internal var itemSpacing by mutableStateOf(0)
private val currentPageLayoutInfo: LazyListItemInfo?
get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
@@ -135,9 +137,7 @@
*/
val currentPageOffset: Float by derivedStateOf {
currentPageLayoutInfo?.let {
- // We coerce since itemSpacing can make the offset > 1f.
- // We don't want to count spacing in the offset so cap it to 1f
- (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f)
+ (-it.offset / (it.size + itemSpacing).toFloat()).coerceIn(-0.5f, 0.5f)
} ?: 0f
}
@@ -187,28 +187,26 @@
// offset from the size
lazyListState.animateScrollToItem(
index = page,
- scrollOffset = (target.size * pageOffset).roundToInt()
+ scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
)
} else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
// If we don't, we use the current page size as a guide
- val currentSize = layoutInfo.visibleItemsInfo.first().size
+ val currentSize = layoutInfo.visibleItemsInfo.first().size + itemSpacing
lazyListState.animateScrollToItem(
index = page,
scrollOffset = (currentSize * pageOffset).roundToInt()
)
// The target should be visible now
- target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
- it.index == page
- }
+ target = layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
- if (target != null && target.size != currentSize) {
+ if (target != null && target.size + itemSpacing != currentSize) {
// If the size we used for calculating the offset differs from the actual
// target page size, we need to scroll again. This doesn't look great,
// but there's not much else we can do.
lazyListState.animateScrollToItem(
index = page,
- scrollOffset = (target.size * pageOffset).roundToInt()
+ scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
)
}
}
@@ -248,7 +246,7 @@
if (pageOffset.absoluteValue > 0.0001f) {
currentPageLayoutInfo?.let {
scroll {
- scrollBy(it.size * pageOffset)
+ scrollBy((it.size + itemSpacing) * pageOffset)
}
}
}
@@ -295,7 +293,7 @@
}
private fun requireCurrentPageOffset(value: Float, name: String) {
- require(value in -1f..1f) { "$name must be >= 0 and <= 1" }
+ require(value in -1f..1f) { "$name must be >= -1 and <= 1" }
}
companion object {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index 97e3ac2..8bfcff8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -16,15 +16,10 @@
package com.android.settingslib.spa.framework.util
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.State
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChangedBy
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.take
/**
* Returns a [Flow] whose values are a list which containing the results of applying the given
@@ -41,33 +36,13 @@
map { list -> list.asyncMap(transform) }
/**
+ * Returns a [Flow] whose values are a list containing only elements matching the given [predicate].
+ */
+inline fun <T> Flow<List<T>>.filterItem(crossinline predicate: (T) -> Boolean): Flow<List<T>> =
+ map { list -> list.filter(predicate) }
+
+/**
* Delays the flow a little bit, wait the other flow's first value.
*/
fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
- combine(otherFlow.distinctUntilChangedBy {}) { value, _ -> value }
-
-/**
- * Returns a [Flow] whose values are generated list by combining the most recently emitted non null
- * values by each flow.
- */
-inline fun <reified T : Any> combineToList(vararg flows: Flow<T?>): Flow<List<T>> = combine(
- flows.asList(),
-) { array: Array<T?> -> array.filterNotNull() }
-
-class StateFlowBridge<T> {
- private val stateFlow = MutableStateFlow<T?>(null)
- val flow = stateFlow.filterNotNull()
-
- fun setIfAbsent(value: T) {
- if (stateFlow.value == null) {
- stateFlow.value = value
- }
- }
-
- @Composable
- fun Sync(state: State<T>) {
- LaunchedEffect(state.value) {
- stateFlow.value = state.value
- }
- }
-}
+ combine(otherFlow.take(1)) { value, _ -> value }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
new file mode 100644
index 0000000..2adfcca
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
@@ -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.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.icu.text.MessageFormat
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import java.util.Locale
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Context.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+ resources.formatString(resId, *arguments)
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Resources.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+ MessageFormat(getString(resId), Locale.getDefault(Locale.Category.FORMAT))
+ .format(mapOf(*arguments))
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.kt
new file mode 100644
index 0000000..494e69b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/StateFlowBridge.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.settingslib.spa.framework.util
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+/** A StateFlow holder which value could be set or sync from [State]. */
+class StateFlowBridge<T> {
+ private val stateFlow = MutableStateFlow<T?>(null)
+ val flow = stateFlow.filterNotNull()
+
+ fun setIfAbsent(value: T) {
+ if (stateFlow.value == null) {
+ stateFlow.value = value
+ }
+ }
+
+ @Composable
+ fun Sync(state: State<T>) {
+ LaunchedEffect(state.value) {
+ stateFlow.value = state.value
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
similarity index 97%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index cd8a02a..7cc9bf7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.illustration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
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 b4852e4..4218489b 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
@@ -158,6 +158,7 @@
private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
val focusRequester = remember { FocusRequester() }
val textStyle = MaterialTheme.typography.bodyLarge
+ val hideKeyboardAction = hideKeyboardAction()
TextField(
value = query,
onValueChange = onQueryChange,
@@ -173,7 +174,7 @@
)
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
- keyboardActions = KeyboardActions(onSearch = hideKeyboardAction()),
+ keyboardActions = KeyboardActions(onSearch = { hideKeyboardAction() }),
singleLine = true,
colors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent,
diff --git a/packages/SettingsLib/Spa/tests/AndroidManifest.xml b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
index e2db594..1fda4e0 100644
--- a/packages/SettingsLib/Spa/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.spa.tests">
+ package="com.android.settingslib.spa.test">
<uses-sdk android:minSdkVersion="21"/>
@@ -26,6 +26,6 @@
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="Tests for SpaLib"
- android:targetPackage="com.android.settingslib.spa.tests">
+ android:targetPackage="com.android.settingslib.spa.test">
</instrumentation>
</manifest>
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
deleted file mode 100644
index 5971895..0000000
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 {
- namespace 'com.android.settingslib.spa.tests'
- compileSdk TARGET_SDK
- buildToolsVersion = BUILD_TOOLS_VERSION
-
- defaultConfig {
- minSdk MIN_SDK
- targetSdk TARGET_SDK
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- }
-
- sourceSets {
- main {
- res.srcDirs = ["res"]
- }
- androidTest {
- 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"]
- }
- buildFeatures {
- compose true
- }
- composeOptions {
- kotlinCompilerExtensionVersion jetpack_compose_compiler_version
- }
-}
-
-dependencies {
- androidTestImplementation project(":spa")
- androidTestImplementation project(":testutils")
- androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
-}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
new file mode 100644
index 0000000..1ca425c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.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.
+ -->
+
+<resources>
+ <string name="test_quantity_strings">{count, plural,
+ =1 {There is one song found.}
+ other {There are # songs found.}
+ }</string>
+
+ <string name="test_quantity_strings_with_param">{count, plural,
+ =1 {There is one song found in {place}.}
+ other {There are # songs found in {place}.}
+ }</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
new file mode 100644
index 0000000..944ef7f
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/KeyboardsTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.compose
+
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Text
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.platform.SoftwareKeyboardController
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalComposeUiApi::class)
+@RunWith(AndroidJUnit4::class)
+class KeyboardsTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ lateinit var keyboardController: SoftwareKeyboardController
+
+ @Test
+ fun hideKeyboardAction_callControllerHide() {
+ lateinit var action: () -> Unit
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
+ action = hideKeyboardAction()
+ }
+ }
+
+ action()
+
+ verify(keyboardController).hide()
+ }
+
+ @Test
+ fun rememberLazyListStateAndHideKeyboardWhenStartScroll_notCallHideInitially() {
+ setLazyColumn(scroll = false)
+
+ verify(keyboardController, never()).hide()
+ }
+
+ @Test
+ fun rememberLazyListStateAndHideKeyboardWhenStartScroll_callHideWhenScroll() {
+ setLazyColumn(scroll = true)
+
+ verify(keyboardController).hide()
+ }
+
+ private fun setLazyColumn(scroll: Boolean) {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalSoftwareKeyboardController provides keyboardController) {
+ val lazyListState = rememberLazyListStateAndHideKeyboardWhenStartScroll()
+ LazyColumn(
+ modifier = Modifier.size(100.dp),
+ state = lazyListState,
+ ) {
+ items(count = 10) {
+ Text(text = it.toString())
+ }
+ }
+ if (scroll) {
+ LaunchedEffect(Unit) {
+ lazyListState.animateScrollToItem(1)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
new file mode 100644
index 0000000..4dcdea9
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.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.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.count
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class FlowsTest {
+ @Test
+ fun mapItem() = runTest {
+ val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+ val outputFlow = inputFlow.mapItem { it.length }
+
+ assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
+ }
+
+ @Test
+ fun asyncMapItem() = runTest {
+ val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+ val outputFlow = inputFlow.asyncMapItem { it.length }
+
+ assertThat(outputFlow.first()).containsExactly(1, 2, 3).inOrder()
+ }
+
+ @Test
+ fun filterItem() = runTest {
+ val inputFlow = flowOf(listOf("A", "BB", "CCC"))
+
+ val outputFlow = inputFlow.filterItem { it.length >= 2 }
+
+ assertThat(outputFlow.first()).containsExactly("BB", "CCC").inOrder()
+ }
+
+ @Test
+ fun waitFirst_otherFlowEmpty() = runTest {
+ val mainFlow = flowOf("A")
+ val otherFlow = emptyFlow<String>()
+
+ val outputFlow = mainFlow.waitFirst(otherFlow)
+
+ assertThat(outputFlow.count()).isEqualTo(0)
+ }
+
+ @Test
+ fun waitFirst_otherFlowOneValue() = runTest {
+ val mainFlow = flowOf("A")
+ val otherFlow = flowOf("B")
+
+ val outputFlow = mainFlow.waitFirst(otherFlow)
+
+ assertThat(outputFlow.toList()).containsExactly("A")
+ }
+
+ @Test
+ fun waitFirst_otherFlowTwoValues() = runTest {
+ val mainFlow = flowOf("A")
+ val otherFlow = flowOf("B", "B")
+
+ val outputFlow = mainFlow.waitFirst(otherFlow)
+
+ assertThat(outputFlow.toList()).containsExactly("A")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
new file mode 100644
index 0000000..2017ad1
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MessageFormatsTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun formatString_one() {
+ val message = context.formatString(R.string.test_quantity_strings, "count" to 1)
+
+ assertThat(message).isEqualTo("There is one song found.")
+ }
+
+ @Test
+ fun formatString_other() {
+ val message = context.formatString(R.string.test_quantity_strings, "count" to 2)
+
+ assertThat(message).isEqualTo("There are 2 songs found.")
+ }
+
+ @Test
+ fun formatString_withParam_one() {
+ val message = context.formatString(
+ R.string.test_quantity_strings_with_param,
+ "count" to 1,
+ "place" to "phone",
+ )
+
+ assertThat(message).isEqualTo("There is one song found in phone.")
+ }
+
+ @Test
+ fun formatString_withParam_other() {
+ val message = context.formatString(
+ R.string.test_quantity_strings_with_param,
+ "count" to 2,
+ "place" to "phone",
+ )
+
+ assertThat(message).isEqualTo("There are 2 songs found in phone.")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
new file mode 100644
index 0000000..e1d9a28
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.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.settingslib.spa.framework.util
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class StateFlowBridgeTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun stateFlowBridge_initial() = runTest {
+ val stateFlowBridge = StateFlowBridge<String>()
+
+ val flow = stateFlowBridge.flow
+
+ val first = flow.firstWithTimeoutOrNull()
+ assertThat(first).isNull()
+ }
+
+ @Test
+ fun stateFlowBridge_setIfAbsent() = runTest {
+ val stateFlowBridge = StateFlowBridge<String>()
+
+ stateFlowBridge.setIfAbsent("A")
+
+ val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
+ assertThat(first).isEqualTo("A")
+ }
+
+ @Test
+ fun stateFlowBridge_sync() = runTest {
+ val stateFlowBridge = StateFlowBridge<String>()
+
+ composeTestRule.setContent {
+ stateFlowBridge.Sync(stateOf("A"))
+ }
+
+ val first = stateFlowBridge.flow.firstWithTimeoutOrNull()
+ assertThat(first).isEqualTo("A")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index 8101a94..f59b0de 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,11 +17,13 @@
package com.android.settingslib.spa.widget.button
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Launch
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.getBoundsInRoot
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -66,4 +68,19 @@
assertThat(clicked).isTrue()
}
+
+ @Test
+ fun twoButtons_positionIsAligned() {
+ composeTestRule.setContent {
+ ActionButtons(
+ listOf(
+ ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+ ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
+ )
+ )
+ }
+
+ assertThat(composeTestRule.onNodeWithText("Open").getBoundsInRoot().top)
+ .isEqualTo(composeTestRule.onNodeWithText("Close").getBoundsInRoot().top)
+ }
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
similarity index 84%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
index fa7a98a..2230d6c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.chart
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.SemanticsPropertyKey
@@ -24,12 +24,6 @@
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.widget.chart.BarChart
-import com.android.settingslib.spa.widget.chart.BarChartData
-import com.android.settingslib.spa.widget.chart.LineChart
-import com.android.settingslib.spa.widget.chart.LineChartData
-import com.android.settingslib.spa.widget.chart.PieChart
-import com.android.settingslib.spa.widget.chart.PieChartData
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,10 +33,10 @@
@get:Rule
val composeTestRule = createComposeRule()
- private val Chart = SemanticsPropertyKey<String>("Chart")
- private var SemanticsPropertyReceiver.chart by Chart
+ private val chart = SemanticsPropertyKey<String>("Chart")
+ private var SemanticsPropertyReceiver.chart by chart
private fun hasChart(chart: String): SemanticsMatcher =
- SemanticsMatcher.expectValue(Chart, chart)
+ SemanticsMatcher.expectValue(this.chart, chart)
@Test
fun line_chart_displayed() {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
similarity index 85%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
index 54abec9..105fdc8 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/IllustrationTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.widget
+package com.android.settingslib.spa.widget.illustration
import androidx.annotation.DrawableRes
import androidx.annotation.RawRes
@@ -29,7 +29,7 @@
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.R
+import com.android.settingslib.spa.test.R
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,8 +39,8 @@
@get:Rule
val composeTestRule = createComposeRule()
- private val DrawableId = SemanticsPropertyKey<Int>("DrawableResId")
- private var SemanticsPropertyReceiver.drawableId by DrawableId
+ private val drawableId = SemanticsPropertyKey<Int>("DrawableResId")
+ private var SemanticsPropertyReceiver.drawableId by drawableId
@Test
fun image_displayed() {
@@ -54,7 +54,7 @@
}
fun hasDrawable(@DrawableRes id: Int): SemanticsMatcher =
- SemanticsMatcher.expectValue(DrawableId, id)
+ SemanticsMatcher.expectValue(drawableId, id)
val isIllustrationNode = hasAnyAncestor(hasDrawable(resId))
composeTestRule.onAllNodes(hasDrawable(resId))
@@ -62,8 +62,8 @@
.assertIsDisplayed()
}
- private val RawId = SemanticsPropertyKey<Int>("RawResId")
- private var SemanticsPropertyReceiver.rawId by RawId
+ private val rawId = SemanticsPropertyKey<Int>("RawResId")
+ private var SemanticsPropertyReceiver.rawId by rawId
@Test
fun empty_lottie_not_displayed() {
@@ -77,7 +77,7 @@
}
fun hasRaw(@RawRes id: Int): SemanticsMatcher =
- SemanticsMatcher.expectValue(RawId, id)
+ SemanticsMatcher.expectValue(rawId, id)
val isIllustrationNode = hasAnyAncestor(hasRaw(resId))
composeTestRule.onAllNodes(hasRaw(resId))
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.kt
new file mode 100644
index 0000000..6ccbbd0
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/HomeScaffoldTest.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.settingslib.spa.widget.scaffold
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class HomeScaffoldTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_isDisplayed() {
+ composeTestRule.setContent {
+ HomeScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun items_areDisplayed() {
+ composeTestRule.setContent {
+ HomeScaffold(title = TITLE) {
+ Text(text = "AAA")
+ Text(text = "BBB")
+ }
+ }
+
+ composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+ composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "title"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
new file mode 100644
index 0000000..9d0501f
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
@@ -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.settingslib.spa.widget.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FooterTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun footer_isEmpty() {
+ composeTestRule.setContent {
+ Footer(footerText = "")
+ }
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun footer_notEmpty_displayed() {
+ composeTestRule.setContent {
+ Footer(footerText = FOOTER_TEXT)
+ }
+
+ composeTestRule.onNodeWithText(FOOTER_TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val FOOTER_TEXT = "Footer text"
+ }
+}
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index de87dde..2c1e1c2 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,7 +24,9 @@
srcs: ["src/**/*.kt"],
static_libs: [
+ "SpaLib",
"androidx.arch.core_core-testing",
+ "androidx.compose.runtime_runtime",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index 81e54c1..dd7058d 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -44,9 +44,17 @@
jvmTarget = '1.8'
freeCompilerArgs = ["-Xjvm-default=all"]
}
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion jetpack_compose_compiler_version
+ }
}
dependencies {
+ api project(":spa")
+
api "androidx.arch.core:core-testing:2.1.0"
api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
api "com.google.truth:truth:1.1.3"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.kt
new file mode 100644
index 0000000..5a3044d
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FakeNavControllerWrapper.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.settingslib.spa.testutils
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spa.framework.compose.NavControllerWrapper
+
+class FakeNavControllerWrapper : NavControllerWrapper {
+ var navigateCalledWith: String? = null
+ var navigateBackIsCalled = false
+
+ override fun navigate(route: String) {
+ navigateCalledWith = route
+ }
+
+ override fun navigateBack() {
+ navigateBackIsCalled = true
+ }
+
+ @Composable
+ fun Wrapper(content: @Composable () -> Unit) {
+ CompositionLocalProvider(LocalNavController provides this) {
+ content()
+ }
+ }
+}
diff --git a/core/java/android/window/BackEvent.aidl b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
similarity index 66%
copy from core/java/android/window/BackEvent.aidl
copy to packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
index 821f1fa..7a11499 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/FlowTestUtil.kt
@@ -14,9 +14,13 @@
* limitations under the License.
*/
-package android.window;
+package com.android.settingslib.spa.testutils
-/**
- * @hide
- */
-parcelable BackEvent;
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.withTimeoutOrNull
+
+suspend fun <T> Flow<T>.firstWithTimeoutOrNull(timeMillis: Long = 500): T? =
+ withTimeoutOrNull(timeMillis) {
+ first()
+ }
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
new file mode 100644
index 0000000..dddda55
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
@@ -0,0 +1,50 @@
+/*
+ * 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 androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun <T> LiveData<T>.getOrAwaitValue(
+ timeout: Long = 1,
+ timeUnit: TimeUnit = TimeUnit.SECONDS,
+ afterObserve: () -> Unit = {},
+): T? {
+ var data: T? = null
+ val latch = CountDownLatch(1)
+ val observer = Observer<T> { newData ->
+ data = newData
+ latch.countDown()
+ }
+ this.observeForever(observer)
+
+ afterObserve()
+
+ try {
+ // Don't wait indefinitely if the LiveData is not set.
+ if (!latch.await(timeout, timeUnit)) {
+ throw TimeoutException("LiveData value was never set.")
+ }
+ } finally {
+ this.removeObserver(observer)
+ }
+
+ return data
+}
diff --git a/packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
similarity index 100%
rename from packages/SettingsLib/Spa/testutils/src/MockitoHelper.kt
rename to packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/MockitoHelper.kt
diff --git a/packages/SettingsLib/Spa/testutils/src/SpaTest.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt
similarity index 100%
rename from packages/SettingsLib/Spa/testutils/src/SpaTest.kt
rename to packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/SpaTest.kt
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 487dbcb..4c144b2 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
@@ -29,7 +29,7 @@
/**
* The config used to load the App List.
*/
-internal data class AppListConfig(
+data class AppListConfig(
val userId: Int,
val showInstantApps: Boolean,
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index 71cf23c..c08169e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spaprivileged.model.app
-import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.Mode
@@ -25,34 +24,41 @@
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+
+interface IAppOpsController {
+ val mode: LiveData<Int>
+ val isAllowed: LiveData<Boolean>
+ get() = mode.map { it == MODE_ALLOWED }
+
+ fun setAllowed(allowed: Boolean)
+
+ @Mode
+ fun getMode(): Int
+}
class AppOpsController(
context: Context,
private val app: ApplicationInfo,
private val op: Int,
-) {
- private val appOpsManager = checkNotNull(context.getSystemService(AppOpsManager::class.java))
+) : IAppOpsController {
+ private val appOpsManager = context.appOpsManager
- val mode: LiveData<Int>
+ override val mode: LiveData<Int>
get() = _mode
- val isAllowed: LiveData<Boolean>
- get() = _mode.map { it == MODE_ALLOWED }
- fun setAllowed(allowed: Boolean) {
+ override fun setAllowed(allowed: Boolean) {
val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
appOpsManager.setMode(op, app.uid, app.packageName, mode)
_mode.postValue(mode)
}
@Mode
- fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
+ override fun getMode(): Int = appOpsManager.checkOpNoThrow(op, app.uid, app.packageName)
private val _mode = object : MutableLiveData<Int>() {
override fun onActive() {
postValue(getMode())
}
-
- override fun onInactive() {
- }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index a618c3d..ae362c8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -74,6 +74,8 @@
fun restrictedModeState(): State<RestrictedMode?>
}
+typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider
+
internal class RestrictionsProviderImpl(
private val context: Context,
private val restrictions: Restrictions,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 8b19c5b..0b45da6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -16,11 +16,12 @@
package com.android.settingslib.spaprivileged.template.app
+import android.content.pm.PackageInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Footer
-import com.android.settingslib.spaprivileged.model.app.PackageManagers
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
@Composable
fun AppInfoPage(
@@ -28,18 +29,16 @@
packageName: String,
userId: Int,
footerText: String,
- content: @Composable () -> Unit,
+ packageManagers: IPackageManagers,
+ content: @Composable PackageInfo.() -> Unit,
) {
+ val packageInfo = remember(packageName, userId) {
+ packageManagers.getPackageInfoAsUser(packageName, userId)
+ } ?: return
RegularScaffold(title = title) {
- val appInfoProvider = remember {
- PackageManagers.getPackageInfoAsUser(packageName, userId)?.let { packageInfo ->
- AppInfoProvider(packageInfo)
- }
- } ?: return@RegularScaffold
+ remember(packageInfo) { AppInfoProvider(packageInfo) }.AppInfo(displayVersion = true)
- appInfoProvider.AppInfo(displayVersion = true)
-
- content()
+ packageInfo.content()
Footer(footerText)
}
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 15766e1..2e0d853 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
@@ -49,13 +49,13 @@
private const val TAG = "AppList"
private const val CONTENT_TYPE_HEADER = "header"
-internal data class AppListState(
+data class AppListState(
val showSystem: State<Boolean>,
val option: State<Int>,
val searchQuery: State<String>,
)
-internal data class AppListInput<T : AppRecord>(
+data class AppListInput<T : AppRecord>(
val config: AppListConfig,
val listModel: AppListModel<T>,
val state: AppListState,
@@ -70,10 +70,13 @@
* This UI element will take the remaining space on the screen to show the App List.
*/
@Composable
-internal fun <T : AppRecord> AppListInput<T>.AppList(
- appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
- loadAppListData(config, listModel, state)
- },
+fun <T : AppRecord> AppListInput<T>.AppList() {
+ AppListImpl { loadAppListData(config, listModel, state) }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListInput<T>.AppListImpl(
+ appListDataSupplier: @Composable () -> State<AppListData<T>?>,
) {
LogCompositions(TAG, config.userId.toString())
val appListData = appListDataSupplier()
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index 28bf832..6d0d7d6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -28,7 +28,7 @@
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
-class AppListItemModel<T : AppRecord>(
+data class AppListItemModel<T : AppRecord>(
val record: T,
val label: String,
val summary: State<String>,
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 d452c74..cb35fb0 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
@@ -47,24 +47,9 @@
primaryUserOnly: Boolean = false,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
header: @Composable () -> Unit = {},
+ appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
appItem: @Composable AppListItemModel<T>.() -> Unit,
) {
- AppListPageImpl(
- title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem,
- ) { it.AppList() }
-}
-
-@Composable
-internal fun <T : AppRecord> AppListPageImpl(
- title: String,
- listModel: AppListModel<T>,
- showInstantApps: Boolean = false,
- primaryUserOnly: Boolean = false,
- moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
- header: @Composable () -> Unit = {},
- appItem: @Composable AppListItemModel<T>.() -> Unit,
- appList: @Composable (input: AppListInput<T>) -> Unit,
-) {
val showSystem = rememberSaveable { mutableStateOf(false) }
SearchScaffold(
title = title,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index c6f41d3..a357832 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -25,11 +25,12 @@
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spaprivileged.model.app.AppOpsController
import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
-import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasGrantPermission
-import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@@ -37,21 +38,24 @@
data class AppOpPermissionRecord(
override val app: ApplicationInfo,
val hasRequestPermission: Boolean,
- var appOpsController: AppOpsController,
+ var appOpsController: IAppOpsController,
) : AppRecord
-abstract class AppOpPermissionListModel(private val context: Context) :
- TogglePermissionAppListModel<AppOpPermissionRecord> {
+abstract class AppOpPermissionListModel(
+ private val context: Context,
+ private val packageManagers: IPackageManagers = PackageManagers,
+) : TogglePermissionAppListModel<AppOpPermissionRecord> {
abstract val appOp: Int
abstract val permission: String
+ /** These not changeable packages will also be hidden from app list. */
private val notChangeablePackages =
setOf("android", "com.android.systemui", context.packageName)
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
userIdFlow.map { userId ->
- PackageManagers.getAppOpPermissionPackages(userId, permission)
+ packageManagers.getAppOpPermissionPackages(userId, permission)
}.combine(appListFlow) { packageNames, appList ->
appList.map { app ->
AppOpPermissionRecord(
@@ -64,14 +68,12 @@
override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
app = app,
- hasRequestPermission = app.hasRequestPermission(permission),
+ hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
appOpsController = AppOpsController(context = context, app = app, op = appOp),
)
override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
- recordListFlow.map { recordList ->
- recordList.filter { it.hasRequestPermission }
- }
+ recordListFlow.filterItem(::isChangeable)
/**
* Defining the default behavior as permissible as long as the package requested this permission
@@ -85,7 +87,9 @@
when (mode.value) {
null -> null
MODE_ALLOWED -> true
- MODE_DEFAULT -> record.app.hasGrantPermission(permission)
+ MODE_DEFAULT -> with(packageManagers) {
+ record.app.hasGrantPermission(permission)
+ }
else -> false
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 8287693..5ae5ada 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Bundle
+import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -38,23 +39,15 @@
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import com.android.settingslib.spaprivileged.model.app.toRoute
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.Dispatchers
-private const val ENTRY_NAME = "AllowControl"
-private const val PERMISSION = "permission"
-private const val PACKAGE_NAME = "rt_packageName"
-private const val USER_ID = "rt_userId"
-private const val PAGE_NAME = "TogglePermissionAppInfoPage"
-private val PAGE_PARAMETER = listOf(
- navArgument(PERMISSION) { type = NavType.StringType },
- navArgument(PACKAGE_NAME) { type = NavType.StringType },
- navArgument(USER_ID) { type = NavType.IntType },
-)
-
internal class TogglePermissionAppInfoPageProvider(
private val appListTemplate: TogglePermissionAppListTemplate,
) : SettingsPageProvider {
@@ -64,11 +57,7 @@
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
- )
- return entryList
+ return listOf(SettingsEntryBuilder.create("AllowControl", owner).build())
}
@Composable
@@ -76,11 +65,22 @@
val permissionType = arguments?.getString(PERMISSION)!!
val packageName = arguments.getString(PACKAGE_NAME)!!
val userId = arguments.getInt(USER_ID)
- val listModel = appListTemplate.rememberModel(permissionType)
- TogglePermissionAppInfoPage(listModel, packageName, userId)
+ appListTemplate.rememberModel(permissionType)
+ .TogglePermissionAppInfoPage(packageName, userId)
}
companion object {
+ private const val PAGE_NAME = "TogglePermissionAppInfoPage"
+ private const val PERMISSION = "permission"
+ private const val PACKAGE_NAME = "rt_packageName"
+ private const val USER_ID = "rt_userId"
+
+ private val PAGE_PARAMETER = listOf(
+ navArgument(PERMISSION) { type = NavType.StringType },
+ navArgument(PACKAGE_NAME) { type = NavType.StringType },
+ navArgument(USER_ID) { type = NavType.IntType },
+ )
+
@Composable
fun navigator(permissionType: String, app: ApplicationInfo) =
navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
@@ -116,43 +116,36 @@
}
}
+@VisibleForTesting
@Composable
-private fun TogglePermissionAppInfoPage(
- listModel: TogglePermissionAppListModel<out AppRecord>,
+internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfoPage(
packageName: String,
userId: Int,
+ packageManagers: IPackageManagers = PackageManagers,
+ restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
) {
AppInfoPage(
- title = stringResource(listModel.pageTitleResId),
+ title = stringResource(pageTitleResId),
packageName = packageName,
userId = userId,
- footerText = stringResource(listModel.footerResId),
+ footerText = stringResource(footerResId),
+ packageManagers = packageManagers,
) {
- val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage
- LaunchedEffect(model, Dispatchers.Default) {
- model.initState()
- }
- RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys))
+ val model = createSwitchModel(applicationInfo)
+ val restrictions = Restrictions(userId, switchRestrictionKeys)
+ RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory)
}
}
@Composable
-private fun <T : AppRecord> createSwitchModel(
- listModel: TogglePermissionAppListModel<T>,
- packageName: String,
- userId: Int,
-): TogglePermissionSwitchModel<T>? {
- val record = remember {
- PackageManagers.getApplicationInfoAsUser(packageName, userId)?.let { app ->
- listModel.transformItem(app)
- }
- } ?: return null
-
+private fun <T : AppRecord> TogglePermissionAppListModel<T>.createSwitchModel(
+ app: ApplicationInfo,
+): TogglePermissionSwitchModel<T> {
val context = LocalContext.current
- val isAllowed = listModel.isAllowed(record)
- return remember {
- TogglePermissionSwitchModel(context, listModel, record, isAllowed)
- }
+ val record = remember(app) { transformItem(app) }
+ val isAllowed = isAllowed(record)
+ return remember(record) { TogglePermissionSwitchModel(context, this, record, isAllowed) }
+ .also { model -> LaunchedEffect(model, Dispatchers.IO) { model.initState() } }
}
private class TogglePermissionSwitchModel<T : AppRecord>(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index a003da8..b08b6df 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -38,19 +38,14 @@
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
-import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
+import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
@Composable
-fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
- RestrictedSwitchPreferenceImpl(model, restrictions, ::RestrictionsProviderImpl)
-}
-
-@Composable
-internal fun RestrictedSwitchPreferenceImpl(
+fun RestrictedSwitchPreference(
model: SwitchPreferenceModel,
restrictions: Restrictions,
- restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
+ restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
) {
if (restrictions.keys.isEmpty()) {
SwitchPreference(model)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
index c4f490e..8d384e8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.settingslib.spaprivileged.tests">
+ package="com.android.settingslib.spaprivileged.test">
<application>
<uses-library android:name="android.test.runner" />
@@ -24,5 +24,5 @@
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="Tests for SpaPrivilegedLib"
- android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+ android:targetPackage="com.android.settingslib.spaprivileged.test" />
</manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
index fb1e09a..bdc0ba8 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -22,5 +22,14 @@
<string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
<!-- Test Permission footer. [DO NOT TRANSLATE] -->
- <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string>
+ <string name="test_permission_footer" translatable="false">Test Permission is for testing.</string>
+
+ <!-- Test App Op Permission title. [DO NOT TRANSLATE] -->
+ <string name="test_app_op_permission_title" translatable="false">Test App Op Permission</string>
+
+ <!-- Test App Op Permission switch title. [DO NOT TRANSLATE] -->
+ <string name="test_app_op_permission_switch_title" translatable="false">Allow Test App Op Permission</string>
+
+ <!-- Test App Op Permission footer. [DO NOT TRANSLATE] -->
+ <string name="test_app_op_permission_footer" translatable="false">Test App Op Permission is for testing.</string>
</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
new file mode 100644
index 0000000..01f4cc6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.framework.compose
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class DisposableBroadcastReceiverAsUserTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var context: Context
+
+ private var registeredBroadcastReceiver: BroadcastReceiver? = null
+
+ @Before
+ fun setUp() {
+ whenever(context.registerReceiverAsUser(any(), any(), any(), any(), any()))
+ .thenAnswer {
+ registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+ null
+ }
+ }
+
+ @Test
+ fun broadcastReceiver_registered() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {}
+ }
+ }
+
+ assertThat(registeredBroadcastReceiver).isNotNull()
+ }
+
+ @Test
+ fun broadcastReceiver_isCalledOnReceive() {
+ var onReceiveIsCalled = false
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {
+ onReceiveIsCalled = true
+ }
+ }
+ }
+
+ registeredBroadcastReceiver!!.onReceive(context, Intent())
+
+ assertThat(onReceiveIsCalled).isTrue()
+ }
+
+ @Test
+ fun broadcastReceiver_onStartIsCalled() {
+ var onStartIsCalled = false
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ DisposableBroadcastReceiverAsUser(
+ intentFilter = IntentFilter(),
+ userHandle = USER_HANDLE,
+ onStart = { onStartIsCalled = true },
+ onReceive = {},
+ )
+ }
+ }
+
+ assertThat(onStartIsCalled).isTrue()
+ }
+
+ private companion object {
+ val USER_HANDLE: UserHandle = UserHandle.of(0)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index 8ca79509..bb56c10 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -36,7 +36,7 @@
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun appInfoLabel_isDisplayed() {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index c3c96c6..f2267f6 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -41,7 +41,7 @@
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun title_isDisplayed() {
@@ -118,12 +118,12 @@
): State<AppListInput<TestAppRecord>?> {
val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
composeTestRule.setContent {
- AppListPageImpl(
+ AppListPage(
title = TITLE,
listModel = TestAppListModel(options),
header = header,
appItem = { AppListItem {} },
- appList = { appListState.value = it },
+ appList = { appListState.value = this },
)
}
return appListState
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
new file mode 100644
index 0000000..abdcd3b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItemTest.kt
@@ -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.settingslib.spaprivileged.template.app
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListSwitchItemTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun appLabel_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+ }
+
+ @Test
+ fun summary_displayed() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(SUMMARY).assertIsDisplayed()
+ }
+
+ @Test
+ fun title_onClick() {
+ var titleClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = { titleClicked = true },
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNodeWithText(LABEL).performClick()
+
+ assertThat(titleClicked).isTrue()
+ }
+
+ @Test
+ fun switch_checkIsNull() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(null),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertDoesNotExist()
+ }
+
+ @Test
+ fun switch_checked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(true),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun switch_notChecked() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun switch_changeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsEnabled()
+ }
+
+ @Test
+ fun switch_notChangeable() {
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(false),
+ onCheckedChange = {},
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsNotEnabled()
+ }
+
+ @Test
+ fun switch_onClick() {
+ var switchClicked = false
+ composeTestRule.setContent {
+ ITEM_MODEL.AppListSwitchItem(
+ onClick = {},
+ checked = stateOf(false),
+ changeable = stateOf(true),
+ onCheckedChange = { switchClicked = true },
+ )
+ }
+
+ composeTestRule.onNode(isToggleable()).performClick()
+
+ assertThat(switchClicked).isTrue()
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ const val LABEL = "Label"
+ const val SUMMARY = "Summary"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val ITEM_MODEL = AppListItemModel(
+ record = object : AppRecord {
+ override val app = APP
+ },
+ label = LABEL,
+ summary = stateOf(SUMMARY),
+ )
+ }
+}
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 df80dd4..2677669 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
@@ -44,7 +44,7 @@
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun whenNoApps() {
@@ -102,7 +102,7 @@
appItem = { AppListItem {} },
bottomPadding = 0.dp,
)
- appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) }
+ appListInput.AppListImpl { stateOf(AppListData(appEntries, option = 0)) }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
new file mode 100644
index 0000000..f1d9abe
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.template.app
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.lifecycle.liveData
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.model.app.IAppOpsController
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.test.R
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class AppOpPermissionAppListTest {
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManagers: IPackageManagers
+
+ private lateinit var listModel: TestAppOpPermissionAppListModel
+
+ @Before
+ fun setUp() = runTest {
+ whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION))
+ .thenReturn(emptySet())
+ listModel = TestAppOpPermissionAppListModel()
+ }
+
+ @Test
+ fun transformItem_recordHasCorrectApp() {
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.app).isSameInstanceAs(APP)
+ }
+
+ @Test
+ fun transformItem_hasRequestPermission() = runTest {
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(true)
+ }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isTrue()
+ }
+
+ @Test
+ fun transformItem_notRequestPermission() = runTest {
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ }
+
+ val record = listModel.transformItem(APP)
+
+ assertThat(record.hasRequestPermission).isFalse()
+ }
+
+ @Test
+ fun filter() = runTest {
+ with(packageManagers) {
+ whenever(APP.hasRequestPermission(PERMISSION)).thenReturn(false)
+ }
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record)))
+
+ val recordList = recordListFlow.firstWithTimeoutOrNull()!!
+ assertThat(recordList).isEmpty()
+ }
+
+ @Test
+ fun isAllowed_allowed() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
+ fun isAllowed_defaultAndHasGrantPermission() {
+ with(packageManagers) {
+ whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true)
+ }
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isTrue()
+ }
+
+ @Test
+ fun isAllowed_defaultAndNotGrantPermission() {
+ with(packageManagers) {
+ whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false)
+ }
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun isAllowed_notAllowed() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED),
+ )
+
+ val isAllowed = getIsAllowed(record)
+
+ assertThat(isAllowed).isFalse()
+ }
+
+ @Test
+ fun isChangeable_notRequestPermission() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = false,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isFalse()
+ }
+
+ @Test
+ fun isChangeable_notChangeablePackages() {
+ val record = AppOpPermissionRecord(
+ app = NOT_CHANGEABLE_APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isFalse()
+ }
+
+ @Test
+ fun isChangeable_hasRequestPermissionAndChangeable() {
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT),
+ )
+
+ val isChangeable = listModel.isChangeable(record)
+
+ assertThat(isChangeable).isTrue()
+ }
+
+ @Test
+ fun setAllowed() {
+ val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT)
+ val record = AppOpPermissionRecord(
+ app = APP,
+ hasRequestPermission = true,
+ appOpsController = appOpsController,
+ )
+
+ listModel.setAllowed(record = record, newAllowed = true)
+
+ assertThat(appOpsController.setAllowedCalledWith).isTrue()
+ }
+
+ private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
+ lateinit var isAllowedState: State<Boolean?>
+ composeTestRule.setContent {
+ isAllowedState = listModel.isAllowed(record)
+ }
+ return isAllowedState.value
+ }
+
+ private inner class TestAppOpPermissionAppListModel :
+ AppOpPermissionListModel(context, packageManagers) {
+ override val pageTitleResId = R.string.test_app_op_permission_title
+ override val switchTitleResId = R.string.test_app_op_permission_switch_title
+ override val footerResId = R.string.test_app_op_permission_footer
+ override val appOp = AppOpsManager.OP_MANAGE_MEDIA
+ override val permission = PERMISSION
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val PACKAGE_NAME = "package.name"
+ const val PERMISSION = "PERMISSION"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val NOT_CHANGEABLE_APP = ApplicationInfo().apply {
+ packageName = "android"
+ }
+ }
+}
+
+private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController {
+ var setAllowedCalledWith: Boolean? = null
+
+ override val mode = liveData { emit(fakeMode) }
+
+ override fun setAllowed(allowed: Boolean) {
+ setAllowedCalledWith = allowed
+ }
+
+ override fun getMode() = fakeMode
+}
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 8e98d8c..066e28a 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
@@ -48,7 +48,7 @@
val composeTestRule = createComposeRule()
@Spy
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Mock
private lateinit var storageStatsManager: StorageStatsManager
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
new file mode 100644
index 0000000..ecad08a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.model.app.IPackageManagers
+import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
+import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppInfoPageTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @get:Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManagers: IPackageManagers
+
+ private val fakeRestrictionsProvider = FakeRestrictionsProvider()
+
+ private val appListTemplate =
+ TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+ private val appInfoPageProvider = TogglePermissionAppInfoPageProvider(appListTemplate)
+
+ @Before
+ fun setUp() {
+ fakeRestrictionsProvider.restrictedMode = NoRestricted
+ whenever(packageManagers.getPackageInfoAsUser(PACKAGE_NAME, USER_ID))
+ .thenReturn(PACKAGE_INFO)
+ }
+
+ @Test
+ fun buildEntry() {
+ val entryList = appInfoPageProvider.buildEntry(null)
+
+ assertThat(entryList).hasSize(1)
+ assertThat(entryList[0].displayName).isEqualTo("AllowControl")
+ }
+
+ @Test
+ fun title_isDisplayed() {
+ val listModel = TestTogglePermissionAppListModel()
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun whenAllowed_switchIsOn() {
+ val listModel = TestTogglePermissionAppListModel(isAllowed = true)
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+ .assertIsOn()
+ }
+
+ @Test
+ fun whenNotAllowed_switchIsOff() {
+ val listModel = TestTogglePermissionAppListModel(isAllowed = false)
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+ .assertIsOff()
+ }
+
+ @Test
+ fun whenNotChangeable_switchNotEnabled() {
+ val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
+ .assertIsDisplayed()
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun footer_isDisplayed() {
+ val listModel = TestTogglePermissionAppListModel()
+
+ setTogglePermissionAppInfoPage(listModel)
+
+ composeTestRule.onNodeWithText(context.getString(listModel.footerResId))
+ .assertIsDisplayed()
+ }
+
+ private fun setTogglePermissionAppInfoPage(listModel: TestTogglePermissionAppListModel) {
+ composeTestRule.setContent {
+ listModel.TogglePermissionAppInfoPage(
+ packageName = PACKAGE_NAME,
+ userId = USER_ID,
+ packageManagers = packageManagers,
+ restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
+ )
+ }
+ }
+
+ private companion object {
+ const val USER_ID = 0
+ const val PACKAGE_NAME = "package.name"
+ val APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ val PACKAGE_INFO = PackageInfo().apply {
+ packageName = PACKAGE_NAME
+ applicationInfo = APP
+ }
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 4bc612a..355dfb6 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -24,7 +24,7 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.test.R
import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -36,7 +36,7 @@
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun appListInjectEntry_titleDisplayed() {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
index af3189f..1818f2d 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -24,8 +24,8 @@
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spaprivileged.R
-import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.test.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -36,7 +36,7 @@
@get:Rule
val composeTestRule = createComposeRule()
- private var context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Test
fun appListInjectEntry_titleDisplayed() {
@@ -70,8 +70,3 @@
assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
}
}
-
-private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
- override val permissionType = "test.PERMISSION"
- override fun createModel(context: Context) = TestTogglePermissionAppListModel()
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
index 7f57025..a13c483 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceTest.kt
@@ -142,7 +142,7 @@
private fun setContent(restrictions: Restrictions) {
composeTestRule.setContent {
- RestrictedSwitchPreferenceImpl(switchPreferenceModel, restrictions) { _, _ ->
+ RestrictedSwitchPreference(switchPreferenceModel, restrictions) { _, _ ->
fakeRestrictionsProvider
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
index 91a9c6b..b13fbb3 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -19,11 +19,14 @@
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.test.R
import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
import kotlinx.coroutines.flow.Flow
-class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> {
+class TestTogglePermissionAppListModel(
+ private val isAllowed: Boolean? = null,
+ private val isChangeable: Boolean = false,
+) : TogglePermissionAppListModel<TestAppRecord> {
override val pageTitleResId = R.string.test_permission_title
override val switchTitleResId = R.string.test_permission_switch_title
override val footerResId = R.string.test_permission_footer
@@ -34,9 +37,9 @@
recordListFlow
@Composable
- override fun isAllowed(record: TestAppRecord) = stateOf(null)
+ override fun isAllowed(record: TestAppRecord) = stateOf(isAllowed)
- override fun isChangeable(record: TestAppRecord) = false
+ override fun isChangeable(record: TestAppRecord) = isChangeable
override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
}
diff --git a/core/java/android/window/BackEvent.aidl b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
similarity index 60%
copy from core/java/android/window/BackEvent.aidl
copy to packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
index 821f1fa..354bbf5 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListProvider.kt
@@ -14,9 +14,12 @@
* limitations under the License.
*/
-package android.window;
+package com.android.settingslib.spaprivileged.tests.testutils
-/**
- * @hide
- */
-parcelable BackEvent;
+import android.content.Context
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+
+object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "test.PERMISSION"
+ override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 61c7fb9..2951001 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -163,6 +163,11 @@
mUnpairing = false;
}
+ /** Clears any pending messages in the message queue. */
+ public void release() {
+ mHandler.removeCallbacksAndMessages(null);
+ }
+
private void initDrawableCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
@@ -1441,11 +1446,13 @@
final boolean tmpJustDiscovered = mJustDiscovered;
final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
// Set main device from sub device
+ release();
mDevice = mSubDevice.mDevice;
mRssi = mSubDevice.mRssi;
mJustDiscovered = mSubDevice.mJustDiscovered;
mHearingAidInfo = mSubDevice.mHearingAidInfo;
// Set sub device from backup
+ mSubDevice.release();
mSubDevice.mDevice = tmpDevice;
mSubDevice.mRssi = tmpRssi;
mSubDevice.mJustDiscovered = tmpJustDiscovered;
@@ -1471,6 +1478,7 @@
* Remove a device from the member device sets.
*/
public void removeMemberDevice(CachedBluetoothDevice memberDevice) {
+ memberDevice.release();
mMemberDevices.remove(memberDevice);
}
@@ -1488,11 +1496,13 @@
final short tmpRssi = mRssi;
final boolean tmpJustDiscovered = mJustDiscovered;
// Set main device from sub device
+ release();
mDevice = newMainDevice.mDevice;
mRssi = newMainDevice.mRssi;
mJustDiscovered = newMainDevice.mJustDiscovered;
// Set sub device from backup
+ newMainDevice.release();
newMainDevice.mDevice = tmpDevice;
newMainDevice.mRssi = tmpRssi;
newMainDevice.mJustDiscovered = tmpJustDiscovered;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index dd56bde..221836b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -223,8 +223,14 @@
public synchronized void clearNonBondedDevices() {
clearNonBondedSubDevices();
- mCachedDevices.removeIf(cachedDevice
- -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+ final List<CachedBluetoothDevice> removedCachedDevice = new ArrayList<>();
+ mCachedDevices.stream()
+ .filter(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
+ .forEach(cachedDevice -> {
+ cachedDevice.release();
+ removedCachedDevice.add(cachedDevice);
+ });
+ mCachedDevices.removeAll(removedCachedDevice);
}
private void clearNonBondedSubDevices() {
@@ -245,6 +251,7 @@
if (subDevice != null
&& subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
// Sub device exists and it is not bonded
+ subDevice.release();
cachedDevice.setSubDevice(null);
}
}
@@ -294,6 +301,7 @@
}
if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
cachedDevice.setJustDiscovered(false);
+ cachedDevice.release();
mCachedDevices.remove(i);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index f06aab3..6b7f733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -176,11 +176,11 @@
* @param device is the device for which we want to know if supports synchronized presets
* @return {@code true} if the device supports synchronized presets
*/
- public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+ public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportSynchronizedPresets(device);
+ return mService.supportsSynchronizedPresets(device);
}
/**
@@ -189,11 +189,11 @@
* @param device is the device for which we want to know if supports independent presets
* @return {@code true} if the device supports independent presets
*/
- public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+ public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportIndependentPresets(device);
+ return mService.supportsIndependentPresets(device);
}
/**
@@ -202,11 +202,11 @@
* @param device is the device for which we want to know if supports dynamic presets
* @return {@code true} if the device supports dynamic presets
*/
- public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+ public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportDynamicPresets(device);
+ return mService.supportsDynamicPresets(device);
}
/**
@@ -215,11 +215,11 @@
* @param device is the device for which we want to know if supports writable presets
* @return {@code true} if the device supports writable presets
*/
- public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+ public boolean supportsWritablePresets(@NonNull BluetoothDevice device) {
if (mService == null) {
return false;
}
- return mService.supportWritablePresets(device);
+ return mService.supportsWritablePresets(device);
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 65671a2..77c19a1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -998,10 +998,12 @@
mCachedDevice.switchSubDeviceContent();
+ verify(mCachedDevice).release();
assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+ verify(mSubCachedDevice).release();
assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 0b7b2f9..9192086 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1144,7 +1144,7 @@
Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
}
- DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
+ Settings.Config.enforceReadPermission(/*namespace=*/name.split("/")[0]);
// Get the value.
synchronized (mLock) {
@@ -1317,7 +1317,7 @@
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
}
- DeviceConfig.enforceReadPermission(
+ Settings.Config.enforceReadPermission(
prefix != null ? prefix.split("/")[0] : null);
synchronized (mLock) {
@@ -3595,8 +3595,8 @@
private Uri getNotificationUriFor(int key, String name) {
if (isConfigSettingsKey(key)) {
- return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
- : DeviceConfig.CONTENT_URI;
+ return (name != null) ? Uri.withAppendedPath(Settings.Config.CONTENT_URI, name)
+ : Settings.Config.CONTENT_URI;
} else if (isGlobalSettingsKey(key)) {
return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
: Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index aa194b9..153f0b4 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -772,6 +772,7 @@
Settings.Secure.SLEEP_TIMEOUT,
Settings.Secure.SMS_DEFAULT_APPLICATION,
Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q
+ Settings.Secure.STYLUS_BUTTONS_DISABLED,
Settings.Secure.TRUST_AGENTS_INITIALIZED,
Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED,
Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index e588b3d..753378b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,7 +22,6 @@
import android.content.ContentResolver;
import android.os.Bundle;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -180,14 +179,14 @@
args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+ Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
}
private static String getFromContentProvider(ContentResolver resolver, String namespace,
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+ Settings.Config.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
assertNotNull(result);
return result.getString(Settings.NameValueTable.VALUE);
}
@@ -196,7 +195,8 @@
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ Settings.Config.CONTENT_URI,
+ Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
assertNotNull(result);
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 680a0a1..d3ba5e6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -754,6 +754,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<!-- Permission required for CTS test - CtsAppFgsTestCases -->
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
+
+ <!-- Permission required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7db68b0..a8216e8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -914,6 +914,29 @@
<service android:name=".controls.controller.AuxiliaryPersistenceWrapper$DeletionJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
+ <!-- region Note Task -->
+ <activity
+ android:name=".notetask.shortcut.CreateNoteTaskShortcutActivity"
+ android:enabled="false"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.NoDisplay"
+ android:label="@string/note_task_button_label"
+ android:icon="@drawable/ic_note_task_button">
+
+ <intent-filter>
+ <action android:name="android.intent.action.CREATE_SHORTCUT" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".notetask.shortcut.LaunchNoteTaskActivity"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:theme="@android:style/Theme.NoDisplay" />
+ <!-- endregion -->
+
<!-- started from ControlsRequestReceiver -->
<activity
android:name=".controls.management.ControlsRequestDialog"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 22944b8..462b90a 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -237,6 +237,28 @@
this.lockScreenColor = lockScreenColor
}
+ fun animateColorChange() {
+ logBuffer?.log(tag, DEBUG, "animateColorChange")
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = null, /* using current color */
+ animate = false,
+ duration = 0,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ setTextStyle(
+ weight = lockScreenWeight,
+ textSize = -1f,
+ color = lockScreenColor,
+ animate = true,
+ duration = COLOR_ANIM_DURATION,
+ delay = 0,
+ onAnimationEnd = null
+ )
+ }
+
fun animateAppearOnLockscreen() {
logBuffer?.log(tag, DEBUG, "animateAppearOnLockscreen")
setTextStyle(
@@ -350,6 +372,7 @@
*
* By passing -1 to weight, the view preserves its current weight.
* By passing -1 to textSize, the view preserves its current text size.
+ * By passing null to color, the view preserves its current color.
*
* @param weight text weight.
* @param textSize font size.
@@ -611,6 +634,7 @@
private const val APPEAR_ANIM_DURATION: Long = 350
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
+ private const val COLOR_ANIM_DURATION: Long = 400
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index e1f2174..c540f0f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -142,7 +142,7 @@
currentColor = color
view.setColors(DOZE_COLOR, color)
if (!animations.dozeState.isActive) {
- view.animateAppearOnLockscreen()
+ view.animateColorChange()
}
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 898935f..2cac9c7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -21,8 +21,6 @@
android:id="@+id/keyguard_bouncer_user_switcher"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
android:orientation="vertical"
android:gravity="center"
android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
index 411fea5..316ad39 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
@@ -18,6 +18,8 @@
<TextView
android:id="@+id/digit_text"
style="@style/Widget.TextView.NumPadKey.Digit"
+ android:autoSizeMaxTextSize="32sp"
+ android:autoSizeTextType="uniform"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_button.xml
new file mode 100644
index 0000000..bb5e224
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_button.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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <path
+ android:fillColor="#636C6F"
+ android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+ <path
+ android:fillColor="#636C6F"
+ android:fillType="evenOdd"
+ android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 530db0d..13c9a5e 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -35,7 +35,6 @@
android:layout_height="@dimen/qs_media_session_height_expanded"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:translationZ="0dp"
android:scaleType="centerCrop"
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 80628f9..f4434e8 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -36,7 +36,4 @@
<integer name="qs_security_footer_maxLines">1</integer>
<bool name="config_use_large_screen_shade_header">true</bool>
-
- <!-- Whether to show the side fps hint while on bouncer -->
- <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index b8e2caf..247e44d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -555,7 +555,7 @@
<string name="config_preferredEmergencySosPackage" translatable="false"></string>
<!-- Whether to show the side fps hint while on bouncer -->
- <bool name="config_show_sidefps_hint_on_bouncer">false</bool>
+ <bool name="config_show_sidefps_hint_on_bouncer">true</bool>
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 49dd574..fd2e324 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -36,4 +36,8 @@
avatar will no longer show on the lockscreen -->
<bool name="flag_user_switcher_chip">false</bool>
+ <!-- Whether the battery icon is allowed to display a shield when battery life is being
+ protected. -->
+ <bool name="flag_battery_shield_icon">false</bool>
+
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a4e9983..9ee918d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2773,6 +2773,10 @@
<xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
</string>
+ <!-- TODO(b/259369672): Replace with final resource. -->
+ <!-- [CHAR LIMIT=30] Label used to open Note Task -->
+ <string name="note_task_button_label">Notetaking</string>
+
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
<string name="broadcasting_description_is_broadcasting">Broadcasting</string>
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index de855e2..c32de70 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -124,20 +124,9 @@
</KeyFrameSet>
</Transition>
- <Transition
- android:id="@+id/large_screen_header_transition"
- app:constraintSetStart="@id/large_screen_header_constraint"
- app:constraintSetEnd="@id/large_screen_header_constraint"/>
+ <Include app:constraintSet="@xml/large_screen_shade_header"/>
- <!--
- Placeholder ConstraintSet. They are populated in the controller for this class.
- This is needed because there's no easy way to just refer to a `ConstraintSet` file. The
- options are either a layout file or inline the ConstraintSets.
- -->
- <ConstraintSet android:id="@id/qqs_header_constraint"/>
+ <Include app:constraintSet="@xml/qs_header"/>
- <ConstraintSet android:id="@id/qs_header_constraint"/>
-
- <ConstraintSet android:id="@id/large_screen_header_constraint" />
-
+ <Include app:constraintSet="@xml/qqs_header"/>
</MotionScene>
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index 5d3650c..e56e5d5 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -59,7 +59,6 @@
<Layout
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"
@@ -75,7 +74,6 @@
<Layout
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/statusIcons"
app:layout_constraintEnd_toEndOf="@id/end_guide"
@@ -112,5 +110,4 @@
app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
-
</ConstraintSet>
\ No newline at end of file
diff --git a/packages/SystemUI/res/xml/qs_header.xml b/packages/SystemUI/res/xml/qs_header.xml
index 982c422..eca2b2a 100644
--- a/packages/SystemUI/res/xml/qs_header.xml
+++ b/packages/SystemUI/res/xml/qs_header.xml
@@ -56,6 +56,7 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="@dimen/new_qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/space"
app:layout_constraintBottom_toBottomOf="parent"
@@ -88,7 +89,6 @@
<Layout
android:layout_width="wrap_content"
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"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc..c985fd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -34,6 +34,7 @@
import android.graphics.Rect;
import android.os.Trace;
import android.util.AttributeSet;
+import android.view.WindowInsets;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.animation.AnimationUtils;
@@ -236,4 +237,50 @@
return getResources().getString(
com.android.internal.R.string.keyguard_accessibility_password_unlock);
}
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
+ mPasswordEntry.requestFocus();
+ }
+ return super.onApplyWindowInsets(insets);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus) {
+ if (isVisibleToUser()) {
+ showKeyboard();
+ } else {
+ hideKeyboard();
+ }
+ }
+ }
+
+ /**
+ * Sends signal to the focused window to show the keyboard.
+ */
+ public void showKeyboard() {
+ post(() -> {
+ if (mPasswordEntry.isAttachedToWindow()
+ && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ mPasswordEntry.requestFocus();
+ mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
+ }
+ });
+ }
+
+ /**
+ * Sends signal to the focused window to hide the keyboard.
+ */
+ public void hideKeyboard() {
+ post(() -> {
+ if (mPasswordEntry.isAttachedToWindow()
+ && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ mPasswordEntry.clearFocus();
+ mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+ });
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 195e8f9..d221e22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -200,12 +199,9 @@
return;
}
- mView.post(() -> {
- if (mView.isShown()) {
- mPasswordEntry.requestFocus();
- mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
- }
- });
+ if (mView.isShown()) {
+ mView.showKeyboard();
+ }
}
@Override
@@ -227,16 +223,12 @@
super.onPause();
});
}
- if (mPasswordEntry.isAttachedToWindow()) {
- mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
+ mView.hideKeyboard();
}
@Override
public void onStartingToHide() {
- if (mPasswordEntry.isAttachedToWindow()) {
- mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
- }
+ mView.hideKeyboard();
}
private void updateSwitchImeButton() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5c4126e..8f3484a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -221,10 +221,11 @@
public void onEnd(WindowInsetsAnimation animation) {
if (!mDisappearAnimRunning) {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
- updateChildren(0 /* translationY */, 1f /* alpha */);
} else {
endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
+ setAlpha(0f);
}
+ updateChildren(0 /* translationY */, 1f /* alpha */);
}
private void updateChildren(int translationY, float alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8019b56..ae2e1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -83,8 +83,7 @@
val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
@JvmField
- val NOTIFICATION_GROUP_CORNER =
- unreleasedFlag(116, "notification_group_corner", teamfood = true)
+ val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true)
// TODO(b/259217907)
@JvmField
@@ -227,7 +226,9 @@
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")
+ @JvmField
+ val BATTERY_SHIELD_ICON =
+ resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
// TODO(b/260881289): Tracking Bug
val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 948239a..36c939d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2209,6 +2209,9 @@
case START_KEYGUARD_EXIT_ANIM:
Trace.beginSection(
"KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
+ synchronized (KeyguardViewMediator.this) {
+ mHiding = true;
+ }
StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(
() -> {
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 827ac78..df8fb91 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
@@ -940,19 +940,9 @@
if (mIsSeekBarEnabled) {
return ConstraintSet.VISIBLE;
}
- // If disabled and "neighbours" are visible, set progress bar to INVISIBLE instead of GONE
- // so layout weights still work.
- return areAnyExpandedBottomActionsVisible() ? ConstraintSet.INVISIBLE : ConstraintSet.GONE;
- }
-
- private boolean areAnyExpandedBottomActionsVisible() {
- ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- for (int id : MediaViewHolder.Companion.getExpandedBottomActionIds()) {
- if (expandedSet.getVisibility(id) == ConstraintSet.VISIBLE) {
- return true;
- }
- }
- return false;
+ // Set progress bar to INVISIBLE to keep the positions of text and buttons similar to the
+ // original positions when seekbar is enabled.
+ return ConstraintSet.INVISIBLE;
}
private void setGenericButton(
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 647beb9..b10abb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -48,52 +48,66 @@
/** All commands for the sender device. */
inner class SenderCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
- val commandName = args[1]
+ if (args.size < 2) {
+ help(pw)
+ return
+ }
+
+ val senderArgs = processArgs(args)
+
@StatusBarManager.MediaTransferSenderState
val displayState: Int?
try {
- displayState = ChipStateSender.getSenderStateIdFromName(commandName)
+ displayState = ChipStateSender.getSenderStateIdFromName(senderArgs.commandName)
} catch (ex: IllegalArgumentException) {
- pw.println("Invalid command name $commandName")
+ pw.println("Invalid command name ${senderArgs.commandName}")
return
}
@SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
as StatusBarManager
- val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0])
+ val routeInfo = MediaRoute2Info.Builder(senderArgs.id, senderArgs.deviceName)
.addFeature("feature")
- val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
- if (useAppIcon) {
+ if (senderArgs.useAppIcon) {
routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
}
+ var undoExecutor: Executor? = null
+ var undoRunnable: Runnable? = null
+ if (isSucceededState(displayState) && senderArgs.showUndo) {
+ undoExecutor = mainExecutor
+ undoRunnable = Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
+ }
+
statusBarManager.updateMediaTapToTransferSenderDisplay(
displayState,
routeInfo.build(),
- getUndoExecutor(displayState),
- getUndoCallback(displayState)
+ undoExecutor,
+ undoRunnable,
)
}
- private fun getUndoExecutor(
- @StatusBarManager.MediaTransferSenderState displayState: Int
- ): Executor? {
- return if (isSucceededState(displayState)) {
- mainExecutor
- } else {
- null
- }
- }
+ private fun processArgs(args: List<String>): SenderArgs {
+ val senderArgs = SenderArgs(
+ deviceName = args[0],
+ commandName = args[1],
+ )
- private fun getUndoCallback(
- @StatusBarManager.MediaTransferSenderState displayState: Int
- ): Runnable? {
- return if (isSucceededState(displayState)) {
- Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
- } else {
- null
+ if (args.size == 2) {
+ return senderArgs
}
+
+ // Process any optional arguments
+ args.subList(2, args.size).forEach {
+ when {
+ it == "useAppIcon=false" -> senderArgs.useAppIcon = false
+ it == "showUndo=false" -> senderArgs.showUndo = false
+ it.substring(0, 3) == "id=" -> senderArgs.id = it.substring(3)
+ }
+ }
+
+ return senderArgs
}
private fun isSucceededState(
@@ -106,14 +120,31 @@
}
override fun help(pw: PrintWriter) {
- pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
- "<deviceName> <chipState> useAppIcon=[true|false] <id>")
+ pw.println(
+ "Usage: adb shell cmd statusbar $SENDER_COMMAND " +
+ "<deviceName> <chipState> " +
+ "useAppIcon=[true|false] id=<id> showUndo=[true|false]"
+ )
+ pw.println("Note: useAppIcon, id, and showUndo are optional additional commands.")
}
}
+ private data class SenderArgs(
+ val deviceName: String,
+ val commandName: String,
+ var id: String = "id",
+ var useAppIcon: Boolean = true,
+ var showUndo: Boolean = true,
+ )
+
/** All commands for the receiver device. */
inner class ReceiverCommand : Command {
override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args.isEmpty()) {
+ help(pw)
+ return
+ }
+
val commandName = args[0]
@StatusBarManager.MediaTransferReceiverState
val displayState: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 120f7d6..b55bedd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -58,6 +58,27 @@
)
}
+ /**
+ * Logs an invalid sender state transition error in trying to update to [desiredState].
+ *
+ * @param currentState the previous state of the chip.
+ * @param desiredState the new state of the chip.
+ */
+ fun logInvalidStateTransitionError(
+ currentState: String,
+ desiredState: String
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.ERROR,
+ {
+ str1 = currentState
+ str2 = desiredState
+ },
+ { "Cannot display state=$str2 after state=$str1; invalid transition" }
+ )
+ }
+
/** Logs that we couldn't find information for [packageName]. */
fun logPackageNotFound(packageName: String) {
buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index af7317c..1f27582 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -56,7 +56,12 @@
R.string.media_move_closer_to_start_cast,
transferStatus = TransferStatus.NOT_STARTED,
endItem = null,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/**
* A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@
R.string.media_move_closer_to_end_cast,
transferStatus = TransferStatus.NOT_STARTED,
endItem = null,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/**
* A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@
transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
+ nextState == TRANSFER_TO_RECEIVER_FAILED
+ }
+ },
/**
* A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@
transferStatus = TransferStatus.IN_PROGRESS,
endItem = SenderEndItem.Loading,
timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
+ nextState == TRANSFER_TO_THIS_DEVICE_FAILED
+ }
+ },
/**
* A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@
newState =
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
),
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_START_CAST ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/**
* A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@
newState =
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
),
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_END_CAST ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/** A state representing that a transfer to the receiver device has failed. */
TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
endItem = SenderEndItem.Error,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_START_CAST ||
+ nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+ }
+ },
/** A state representing that a transfer back to this device has failed. */
TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@
R.string.media_transfer_failed,
transferStatus = TransferStatus.FAILED,
endItem = SenderEndItem.Error,
- ),
+ ) {
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState == ALMOST_CLOSE_TO_END_CAST ||
+ nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+ }
+ },
/** A state representing that this device is far away from any receiver device. */
FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@
throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
"so its string should never be fetched")
}
+
+ override fun isValidNextState(nextState: ChipStateSender): Boolean {
+ return nextState == FAR_FROM_RECEIVER ||
+ nextState.transferStatus == TransferStatus.NOT_STARTED ||
+ nextState.transferStatus == TransferStatus.IN_PROGRESS
+ }
};
/**
@@ -175,6 +227,8 @@
return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
}
+ abstract fun isValidNextState(nextState: ChipStateSender): Boolean
+
companion object {
/**
* Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@
*/
@StatusBarManager.MediaTransferSenderState
fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
+
+ /**
+ * Validates the transition from a chip state to another.
+ *
+ * @param currentState is the current state of the chip.
+ * @param desiredState is the desired state of the chip.
+ * @return true if the transition from [currentState] to [desiredState] is valid, and false
+ * otherwise.
+ */
+ fun isValidStateTransition(
+ currentState: ChipStateSender?,
+ desiredState: ChipStateSender,
+ ): Boolean {
+ // Far from receiver is the default state.
+ if (currentState == null) {
+ return FAR_FROM_RECEIVER.isValidNextState(desiredState)
+ }
+
+ // No change in state is valid.
+ if (currentState == desiredState) {
+ return true
+ }
+
+ return currentState.isValidNextState(desiredState)
+ }
}
}
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 bb7bc6f..e34b0cb 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
@@ -52,6 +52,8 @@
) : CoreStartable {
private var displayedState: ChipStateSender? = null
+ // A map to store current chip state per id.
+ private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
private val commandQueueCallbacks =
object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@
logger.logStateChangeError(displayState)
return
}
+
+ val currentState = stateMap[routeInfo.id]
+ if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+ // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
+ logger.logInvalidStateTransitionError(
+ currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+ chipState.name
+ )
+ return
+ }
uiEventLogger.logSenderStateChange(chipState)
+ stateMap.put(routeInfo.id, chipState)
if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+ // No need to store the state since it is the default state
+ stateMap.remove(routeInfo.id)
// Return early if we're not displaying a chip anyway
val currentDisplayedState = displayedState ?: return
@@ -119,7 +134,7 @@
context,
logger,
)
- )
+ ) { stateMap.remove(routeInfo.id) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3fd1aa7..e2f55f0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -145,7 +145,7 @@
boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
// TODO(b/243765256): Disable this logging once b/243765256 is fixed.
- Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+ " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
+ " willApplyConfigToNavbars=" + willApplyConfig
+ " navBarCount=" + mNavigationBars.size());
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 26d3902..f97385b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -977,7 +977,7 @@
}
// TODO(b/243765256): Disable this logging once b/243765256 is fixed.
- Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+ " lastReportedConfig=" + mLastReportedConfig);
mLastReportedConfig.updateFrom(newConfig);
updateDisplaySize();
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index b964b76..6dd60d0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,10 +17,12 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.content.ComponentName
import android.content.Context
+import android.content.pm.PackageManager
import android.os.UserManager
-import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -45,15 +47,22 @@
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
- fun handleSystemKey(keyCode: Int) {
+ /**
+ * Shows a note task. How the task is shown will depend on when the method is invoked.
+ *
+ * If in multi-window mode, notes will open as a full screen experience. That is particularly
+ * important for Large screen devices. These devices may support a taskbar that let users to
+ * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour.
+ *
+ * If the keyguard is locked, notes will open as a full screen experience. A locked device has
+ * no contextual information which let us use the whole screen space available.
+ *
+ * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience.
+ * That will let users open other apps in full screen, and take contextual notes.
+ */
+ fun showNoteTask(isInMultiWindowMode: Boolean = false) {
if (!isEnabled) return
- if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
- showNoteTask()
- }
- }
-
- private fun showNoteTask() {
val bubbles = optionalBubbles.getOrNull() ?: return
val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
val userManager = optionalUserManager.getOrNull() ?: return
@@ -62,11 +71,35 @@
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
- if (keyguardManager.isKeyguardLocked) {
+ if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
context.startActivity(intent)
} else {
// TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
bubbles.showAppBubble(intent)
}
}
+
+ /**
+ * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut
+ * component to [value].
+ *
+ * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
+ * Widget Picker to all users.
+ */
+ fun setNoteTaskShortcutEnabled(value: Boolean) {
+ val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+
+ val enabledState =
+ if (value) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ } else {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ }
+
+ context.packageManager.setComponentEnabledSetting(
+ componentName,
+ enabledState,
+ PackageManager.DONT_KILL_APP,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 0a5b600..d14b7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,9 +16,10 @@
package com.android.systemui.notetask
+import android.view.KeyEvent
+import androidx.annotation.VisibleForTesting
import com.android.systemui.statusbar.CommandQueue
import com.android.wm.shell.bubbles.Bubbles
-import dagger.Lazy
import java.util.Optional
import javax.inject.Inject
@@ -27,15 +28,18 @@
@Inject
constructor(
private val optionalBubbles: Optional<Bubbles>,
- private val lazyNoteTaskController: Lazy<NoteTaskController>,
+ private val noteTaskController: NoteTaskController,
private val commandQueue: CommandQueue,
@NoteTaskEnabledKey private val isEnabled: Boolean,
) {
- private val callbacks =
+ @VisibleForTesting
+ val callbacks =
object : CommandQueue.Callbacks {
override fun handleSystemKey(keyCode: Int) {
- lazyNoteTaskController.get().handleSystemKey(keyCode)
+ if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+ noteTaskController.showNoteTask()
+ }
}
}
@@ -43,5 +47,6 @@
if (isEnabled && optionalBubbles.isPresent) {
commandQueue.addCallback(callbacks)
}
+ noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 035396a..8bdf319 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -16,32 +16,47 @@
package com.android.systemui.notetask
+import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.os.UserManager
import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import dagger.Binds
import dagger.Module
import dagger.Provides
-import java.util.*
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
/** Compose all dependencies required by Note Task feature. */
@Module
-internal class NoteTaskModule {
+internal interface NoteTaskModule {
- @[Provides NoteTaskEnabledKey]
- fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
- return featureFlags.isEnabled(Flags.NOTE_TASKS)
- }
+ @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
+ fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
- @Provides
- fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
- return Optional.ofNullable(context.getSystemService())
- }
+ @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
+ fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
- @Provides
- fun provideOptionalUserManager(context: Context): Optional<UserManager> {
- return Optional.ofNullable(context.getSystemService())
+ companion object {
+
+ @[Provides NoteTaskEnabledKey]
+ fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+ return featureFlags.isEnabled(Flags.NOTE_TASKS)
+ }
+
+ @Provides
+ fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
+
+ @Provides
+ fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+ return Optional.ofNullable(context.getSystemService())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
new file mode 100644
index 0000000..f6a623e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -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 com.android.systemui.notetask.shortcut
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.annotation.DrawableRes
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Activity responsible for create a shortcut for notes action. If the shortcut is enabled, a new
+ * shortcut will appear in the widget picker. If the shortcut is selected, the Activity here will be
+ * launched, creating a new shortcut for [CreateNoteTaskShortcutActivity], and will finish.
+ *
+ * @see <a
+ * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
+ * a custom shortcut activity</a>
+ */
+internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val intent =
+ createShortcutIntent(
+ id = SHORTCUT_ID,
+ shortLabel = getString(R.string.note_task_button_label),
+ intent = LaunchNoteTaskActivity.newIntent(context = this),
+ iconResource = R.drawable.ic_note_task_button,
+ )
+ setResult(Activity.RESULT_OK, intent)
+
+ finish()
+ }
+
+ private fun createShortcutIntent(
+ id: String,
+ shortLabel: String,
+ intent: Intent,
+ @DrawableRes iconResource: Int,
+ ): Intent {
+ val shortcutInfo =
+ ShortcutInfoCompat.Builder(this, id)
+ .setIntent(intent)
+ .setShortLabel(shortLabel)
+ .setLongLived(true)
+ .setIcon(IconCompat.createWithResource(this, iconResource))
+ .build()
+
+ return ShortcutManagerCompat.createShortcutResultIntent(
+ this,
+ shortcutInfo,
+ )
+ }
+
+ private companion object {
+ private const val SHORTCUT_ID = "note-task-shortcut-id"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
new file mode 100644
index 0000000..47fe676
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.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.notetask.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskIntentResolver
+import javax.inject.Inject
+
+/** Activity responsible for launching the note experience, and finish. */
+internal class LaunchNoteTaskActivity
+@Inject
+constructor(
+ private val noteTaskController: NoteTaskController,
+) : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ noteTaskController.showNoteTask(isInMultiWindowMode)
+
+ finish()
+ }
+
+ companion object {
+
+ /** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
+ fun newIntent(context: Context): Intent {
+ return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+ // Intent's action must be set in shortcuts, or an exception will be thrown.
+ // TODO(b/254606432): Use Intent.ACTION_NOTES instead.
+ action = NoteTaskIntentResolver.NOTES_ACTION
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 67bc769..5dbf0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -247,7 +247,7 @@
Icon icon;
ContentDescription contentDescription = null;
- if (isParentalControlsEnabled) {
+ if (isParentalControlsEnabled && securityModel.getDeviceAdminIcon() != null) {
icon = new Icon.Loaded(securityModel.getDeviceAdminIcon(), contentDescription);
} else if (vpnName != null || vpnNameWorkProfile != null) {
if (securityModel.isVpnBranded()) {
@@ -476,7 +476,7 @@
@VisibleForTesting
View createDialogView(Context quickSettingsContext) {
if (mSecurityController.isParentalControlsEnabled()) {
- return createParentalControlsDialogView();
+ return createParentalControlsDialogView(quickSettingsContext);
}
return createOrganizationDialogView(quickSettingsContext);
}
@@ -579,8 +579,8 @@
return dialogView;
}
- private View createParentalControlsDialogView() {
- View dialogView = LayoutInflater.from(mContext)
+ private View createParentalControlsDialogView(Context quickSettingsContext) {
+ View dialogView = LayoutInflater.from(quickSettingsContext)
.inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
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 350d8b0..9542a02 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -188,8 +188,6 @@
int mWifiSignalIconId;
@Nullable
String mSsid;
- boolean mActivityIn;
- boolean mActivityOut;
@Nullable
String mWifiSignalContentDescription;
boolean mIsTransient;
@@ -207,8 +205,6 @@
.append(",mConnected=").append(mConnected)
.append(",mWifiSignalIconId=").append(mWifiSignalIconId)
.append(",mSsid=").append(mSsid)
- .append(",mActivityIn=").append(mActivityIn)
- .append(",mActivityOut=").append(mActivityOut)
.append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
.append(",mIsTransient=").append(mIsTransient)
.append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
@@ -226,8 +222,6 @@
CharSequence mDataContentDescription;
int mMobileSignalIconId;
int mQsTypeIcon;
- boolean mActivityIn;
- boolean mActivityOut;
boolean mNoSim;
boolean mRoaming;
boolean mMultipleSubs;
@@ -243,8 +237,6 @@
.append(",mDataContentDescription=").append(mDataContentDescription)
.append(",mMobileSignalIconId=").append(mMobileSignalIconId)
.append(",mQsTypeIcon=").append(mQsTypeIcon)
- .append(",mActivityIn=").append(mActivityIn)
- .append(",mActivityOut=").append(mActivityOut)
.append(",mNoSim=").append(mNoSim)
.append(",mRoaming=").append(mRoaming)
.append(",mMultipleSubs=").append(mMultipleSubs)
@@ -275,8 +267,6 @@
mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
mWifiInfo.mEnabled = indicators.enabled;
mWifiInfo.mSsid = indicators.description;
- mWifiInfo.mActivityIn = indicators.activityIn;
- mWifiInfo.mActivityOut = indicators.activityOut;
mWifiInfo.mIsTransient = indicators.isTransient;
mWifiInfo.mStatusLabel = indicators.statusLabel;
refreshState(mWifiInfo);
@@ -297,8 +287,6 @@
? indicators.typeContentDescriptionHtml : null;
mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
mCellularInfo.mQsTypeIcon = indicators.qsType;
- mCellularInfo.mActivityIn = indicators.activityIn;
- mCellularInfo.mActivityOut = indicators.activityOut;
mCellularInfo.mRoaming = indicators.roaming;
mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
refreshState(mCellularInfo);
@@ -428,8 +416,6 @@
state.state = Tile.STATE_ACTIVE;
state.dualTarget = true;
state.value = cb.mEnabled;
- state.activityIn = cb.mEnabled && cb.mActivityIn;
- state.activityOut = cb.mEnabled && cb.mActivityOut;
final StringBuffer minimalContentDescription = new StringBuffer();
final StringBuffer minimalStateDescription = new StringBuffer();
final Resources r = mContext.getResources();
@@ -503,8 +489,6 @@
boolean mobileDataEnabled = mDataController.isMobileDataSupported()
&& mDataController.isMobileDataEnabled();
state.value = mobileDataEnabled;
- state.activityIn = mobileDataEnabled && cb.mActivityIn;
- state.activityOut = mobileDataEnabled && cb.mActivityOut;
state.expandedAccessibilityClassName = Switch.class.getName();
if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 547b496..00d129a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -565,13 +565,25 @@
statusBarWinController.registerCallback(mStatusBarWindowCallback);
mScreenshotHelper = new ScreenshotHelper(context);
- // Listen for tracing state changes
commandQueue.addCallback(new CommandQueue.Callbacks() {
+
+ // Listen for tracing state changes
@Override
public void onTracingStateChanged(boolean enabled) {
mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
.commitUpdate(mContext.getDisplayId());
}
+
+ @Override
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ if (mOverviewProxy != null) {
+ try {
+ mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
+ } catch (RemoteException e) {
+ Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
+ }
+ }
+ }
});
mCommandQueue = commandQueue;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index 5e47d6d..b511b54 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -46,7 +46,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -73,11 +72,9 @@
* expansion of the headers in small screen portrait.
*
* [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
- * [MotionLayout] has 2 transitions:
+ * [MotionLayout] has one transitions:
* * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
* handheld device configuration.
- * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
- * other configurations
*/
@CentralSurfacesScope
class LargeScreenShadeHeaderController @Inject constructor(
@@ -104,8 +101,6 @@
@VisibleForTesting
internal val HEADER_TRANSITION_ID = R.id.header_transition
@VisibleForTesting
- internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
- @VisibleForTesting
internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
@VisibleForTesting
internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
@@ -120,10 +115,6 @@
}
}
- init {
- loadConstraints()
- }
-
private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
private lateinit var iconManager: StatusBarIconController.TintedIconManager
@@ -345,11 +336,11 @@
if (header is MotionLayout) {
// 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))
+ .load(context, resources.getXml(R.xml.qqs_header))
header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qs_header))
+ .load(context, resources.getXml(R.xml.qs_header))
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.large_screen_shade_header))
+ .load(context, resources.getXml(R.xml.large_screen_shade_header))
}
}
@@ -438,7 +429,6 @@
}
header as MotionLayout
if (largeScreenActive) {
- header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
} else {
header.setTransition(HEADER_TRANSITION_ID)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 6f71b72..507dec6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -465,7 +465,11 @@
private boolean mQsTouchAboveFalsingThreshold;
private int mQsFalsingThreshold;
- /** Indicates drag starting height when swiping down or up on heads-up notifications */
+ /**
+ * Indicates drag starting height when swiping down or up on heads-up notifications.
+ * This usually serves as a threshold from when shade expansion should really start. Otherwise
+ * this value would be height of shade and it will be immediately expanded to some extent.
+ */
private int mHeadsUpStartHeight;
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private boolean mListenForHeadsUp;
@@ -915,6 +919,7 @@
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
+ mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -932,7 +937,6 @@
unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
}
});
- mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
dumpManager.registerDumpable(this);
}
@@ -1108,6 +1112,7 @@
mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
mKeyguardStatusViewController.init();
+ updateClockAppearance();
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
@@ -3411,9 +3416,12 @@
&& mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
boolean goingBetweenClosedShadeAndExpandedQs =
mQsExpandImmediate || collapsingShadeFromExpandedQs;
- // we don't want to update QS expansion when HUN is visible because then the whole shade is
- // initially hidden, even though it has non-zero height
- if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) {
+ // in split shade we react when HUN is visible only if shade height is over HUN start
+ // height - which means user is swiping down. Otherwise shade QS will either not show at all
+ // with HUN movement or it will blink when touching HUN initially
+ boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled
+ || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight);
+ if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) {
float qsExpansionFraction;
if (mSplitShadeEnabled) {
qsExpansionFraction = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1dd3a96..590a04a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -164,6 +164,8 @@
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
+ private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
+ private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -478,6 +480,16 @@
* @see IStatusBar#showRearDisplayDialog
*/
default void showRearDisplayDialog(int currentBaseState) {}
+
+ /**
+ * @see IStatusBar#goToFullscreenFromSplit
+ */
+ default void goToFullscreenFromSplit() {}
+
+ /**
+ * @see IStatusBar#enterStageSplitFromRunningApp
+ */
+ default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
}
public CommandQueue(Context context) {
@@ -1239,6 +1251,14 @@
}
@Override
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ synchronized (mLock) {
+ mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
+ leftOrTop).sendToTarget();
+ }
+ }
+
+ @Override
public void requestAddTile(
@NonNull ComponentName componentName,
@NonNull CharSequence appName,
@@ -1299,6 +1319,11 @@
.sendToTarget();
}
+ @Override
+ public void goToFullscreenFromSplit() {
+ mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -1738,6 +1763,17 @@
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
}
+ break;
+ case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).goToFullscreenFromSplit();
+ }
+ break;
+ case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d7eddf5..56c34a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,6 +39,7 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -66,6 +67,8 @@
// the next icon has translated out of the way, to avoid overlapping.
private static final Interpolator ICON_ALPHA_INTERPOLATOR =
new PathInterpolator(0.6f, 0f, 0.6f, 0f);
+ private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+ private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
private NotificationIconContainer mShelfIcons;
private int[] mTmp = new int[2];
@@ -112,19 +115,24 @@
setClipChildren(false);
setClipToPadding(false);
mShelfIcons.setIsStaticLayout(false);
- requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
- requestTopRoundness(1f, false, SourceType.DefaultValue);
+ requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false);
- // 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);
+ if (!mUseRoundnessSourceTypes) {
+ // 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);
+ }
updateResources();
}
public void bind(AmbientState ambientState,
- NotificationStackScrollLayoutController hostLayoutController) {
+ NotificationStackScrollLayoutController hostLayoutController) {
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
+ hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
+ child.requestRoundnessReset(SHELF_SCROLL);
+ });
}
private void updateResources() {
@@ -185,9 +193,11 @@
+ " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
}
- /** Update the state of the shelf. */
+ /**
+ * Update the state of the shelf.
+ */
public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
ShelfState viewState = (ShelfState) getViewState();
if (mShowNotificationShelf && lastView != null) {
@@ -246,7 +256,7 @@
/**
* @param fractionToShade Fraction of lockscreen to shade transition
- * @param shortestWidth Shortest width to use for lockscreen shelf
+ * @param shortestWidth Shortest width to use for lockscreen shelf
*/
@VisibleForTesting
public void updateActualWidth(float fractionToShade, float shortestWidth) {
@@ -281,9 +291,9 @@
/**
* @param localX Click x from left of screen
- * @param slop Margin of error within which we count x for valid click
- * @param left Left of shelf, from left of screen
- * @param right Right of shelf, from left of screen
+ * @param slop Margin of error within which we count x for valid click
+ * @param left Left of shelf, from left of screen
+ * @param right Right of shelf, from left of screen
* @return Whether click x was in view
*/
@VisibleForTesting
@@ -293,8 +303,8 @@
/**
* @param localY Click y from top of shelf
- * @param slop Margin of error within which we count y for valid click
- * @param top Top of shelf
+ * @param slop Margin of error within which we count y for valid click
+ * @param top Top of shelf
* @param bottom Height of shelf
* @return Whether click y was in view
*/
@@ -306,7 +316,7 @@
/**
* @param localX Click x
* @param localY Click y
- * @param slop Margin of error for valid click
+ * @param slop Margin of error for valid click
* @return Whether this click was on the visible (non-clipped) part of the shelf
*/
@Override
@@ -478,13 +488,15 @@
}
}
- private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart,
+ private void updateCornerRoundnessOnScroll(
+ ActivatableNotificationView anv,
+ float viewStart,
float shelfStart) {
final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard()
&& !mAmbientState.isShadeExpanded()
&& anv instanceof ExpandableNotificationRow
- && ((ExpandableNotificationRow) anv).isHeadsUp();
+ && anv.isHeadsUp();
final boolean isHunGoingToShade = mAmbientState.isShadeExpanded()
&& anv == mAmbientState.getTrackedHeadsUpRow();
@@ -506,41 +518,40 @@
* mAmbientState.getExpansionFraction();
final float cornerAnimationTop = shelfStart - cornerAnimationDistance;
- if (viewEnd >= cornerAnimationTop) {
- // Round bottom corners within animation bounds
- final float changeFraction = MathUtils.saturate(
- (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
- anv.requestBottomRoundness(
- /* value = */ anv.isLastInSection() ? 1f : changeFraction,
- /* animate = */ false,
- SourceType.OnScroll);
-
- } else if (viewEnd < cornerAnimationTop) {
- // Fast scroll skips frames and leaves corners with unfinished rounding.
- // Reset top and bottom corners outside of animation bounds.
- anv.requestBottomRoundness(
- /* value = */ anv.isLastInSection() ? 1f : 0f,
- /* animate = */ false,
- SourceType.OnScroll);
+ final SourceType sourceType;
+ if (mUseRoundnessSourceTypes) {
+ sourceType = SHELF_SCROLL;
+ } else {
+ sourceType = LegacySourceType.OnScroll;
}
- if (viewStart >= cornerAnimationTop) {
+ final float topValue;
+ if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) {
+ topValue = 1f;
+ } else if (viewStart >= cornerAnimationTop) {
// Round top corners within animation bounds
- final float changeFraction = MathUtils.saturate(
+ topValue = MathUtils.saturate(
(viewStart - cornerAnimationTop) / cornerAnimationDistance);
- anv.requestTopRoundness(
- /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
- /* animate = */ false,
- SourceType.OnScroll);
-
- } else if (viewStart < cornerAnimationTop) {
+ } else {
// Fast scroll skips frames and leaves corners with unfinished rounding.
// Reset top and bottom corners outside of animation bounds.
- anv.requestTopRoundness(
- /* value = */ anv.isFirstInSection() ? 1f : 0f,
- /* animate = */ false,
- SourceType.OnScroll);
+ topValue = 0f;
}
+ anv.requestTopRoundness(topValue, sourceType, /* animate = */ false);
+
+ final float bottomValue;
+ if (!mUseRoundnessSourceTypes && anv.isLastInSection()) {
+ bottomValue = 1f;
+ } else if (viewEnd >= cornerAnimationTop) {
+ // Round bottom corners within animation bounds
+ bottomValue = MathUtils.saturate(
+ (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
+ } else {
+ // Fast scroll skips frames and leaves corners with unfinished rounding.
+ // Reset top and bottom corners outside of animation bounds.
+ bottomValue = 0f;
+ }
+ anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
}
/**
@@ -626,10 +637,11 @@
/**
* Update the clipping of this view.
+ *
* @return the amount that our own top should be clipped
*/
private int updateNotificationClipHeight(ExpandableView view,
- float notificationClipEnd, int childIndex) {
+ float notificationClipEnd, int childIndex) {
float viewEnd = view.getTranslationY() + view.getActualHeight();
boolean isPinned = (view.isPinned() || view.isHeadsUpAnimatingAway())
&& !mAmbientState.isDozingAndNotPulsing(view);
@@ -657,7 +669,7 @@
@Override
public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
- int outlineTranslation) {
+ int outlineTranslation) {
if (!mHasItemsInStableShelf) {
shadowIntensity = 0.0f;
}
@@ -665,18 +677,24 @@
}
/**
- * @param i Index of the view in the host layout.
- * @param view The current ExpandableView.
- * @param scrollingFast Whether we are scrolling fast.
+ * @param i Index of the view in the host layout.
+ * @param view The current ExpandableView.
+ * @param scrollingFast Whether we are scrolling fast.
* @param expandingAnimated Whether we are expanding a notification.
- * @param isLastChild Whether this is the last view.
- * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
+ * @param isLastChild Whether this is the last view.
+ * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
* @return The amount how much this notification is in the shelf.
- * 0f is not in shelf. 1f is completely in shelf.
+ * 0f is not in shelf. 1f is completely in shelf.
*/
@VisibleForTesting
- public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
- boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
+ public float getAmountInShelf(
+ int i,
+ ExpandableView view,
+ boolean scrollingFast,
+ boolean expandingAnimated,
+ boolean isLastChild,
+ float shelfClipStart
+ ) {
// Let's calculate how much the view is in the shelf
float viewStart = view.getTranslationY();
@@ -755,8 +773,13 @@
return start;
}
- private void updateIconPositioning(ExpandableView view, float iconTransitionAmount,
- boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
+ private void updateIconPositioning(
+ ExpandableView view,
+ float iconTransitionAmount,
+ boolean scrollingFast,
+ boolean expandingAnimated,
+ boolean isLastChild
+ ) {
StatusBarIconView icon = view.getShelfIcon();
NotificationIconContainer.IconState iconState = getIconState(icon);
if (iconState == null) {
@@ -817,7 +840,7 @@
|| row.showingPulsing()
|| row.getTranslationZ() > mAmbientState.getBaseZHeight();
- iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
+ iconState.iconAppearAmount = iconState.hidden ? 0f : transitionAmount;
// Fade in icons at shelf start
// This is important for conversation icons, which are badged and need x reset
@@ -847,7 +870,7 @@
}
private float getFullyClosedTranslation() {
- return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+ return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
}
@Override
@@ -904,7 +927,7 @@
/**
* @return whether the shelf has any icons in it when a potential animation has finished, i.e
- * if the current state would be applied right now
+ * if the current state would be applied right now
*/
public boolean hasItemsInStableShelf() {
return mHasItemsInStableShelf;
@@ -962,7 +985,7 @@
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
+ int oldTop, int oldRight, int oldBottom) {
updateRelativeOffset();
}
@@ -981,12 +1004,11 @@
/**
* This method resets the OnScroll roundness of a view to 0f
- *
+ * <p>
* Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
*/
- public static void resetOnScrollRoundness(ExpandableView expandableView) {
- expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
- expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+ public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) {
+ expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
}
public class ShelfState extends ExpandableViewState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 3b1fa17..bb84c75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -18,6 +18,8 @@
import android.view.View;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
@@ -42,14 +44,17 @@
private AmbientState mAmbientState;
@Inject
- public NotificationShelfController(NotificationShelf notificationShelf,
+ public NotificationShelfController(
+ NotificationShelf notificationShelf,
ActivatableNotificationViewController activatableNotificationViewController,
KeyguardBypassController keyguardBypassController,
- SysuiStatusBarStateController statusBarStateController) {
+ SysuiStatusBarStateController statusBarStateController,
+ FeatureFlags featureFlags) {
mView = notificationShelf;
mActivatableNotificationViewController = activatableNotificationViewController;
mKeyguardBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
+ mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -88,7 +93,7 @@
public @View.Visibility int getVisibility() {
return mView.getVisibility();
- };
+ }
public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
mView.setCollapsedIcons(notificationIcons);
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 99ff06a..336356e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -68,7 +68,7 @@
import com.android.systemui.R;
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.LongRunning;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -78,6 +78,7 @@
import com.android.systemui.plugins.log.LogLevel;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -192,6 +193,7 @@
private final Executor mBgExecutor;
// Handler that all callbacks are made on.
private final CallbackHandler mCallbackHandler;
+ private final StatusBarPipelineFlags mStatusBarPipelineFlags;
private int mEmergencySource;
private boolean mIsEmergency;
@@ -223,12 +225,15 @@
/**
* Construct this controller object and register for updates.
+ *
+ * {@code @LongRunning} looper and bgExecutor instead {@code @Background} ones are used to
+ * address the b/246456655. This can be reverted after b/240663726 is fixed.
*/
@Inject
public NetworkControllerImpl(
Context context,
- @Background Looper bgLooper,
- @Background Executor bgExecutor,
+ @LongRunning Looper bgLooper,
+ @LongRunning Executor bgExecutor,
SubscriptionManager subscriptionManager,
CallbackHandler callbackHandler,
DeviceProvisionedController deviceProvisionedController,
@@ -239,6 +244,7 @@
TelephonyListenerManager telephonyListenerManager,
@Nullable WifiManager wifiManager,
AccessPointControllerImpl accessPointController,
+ StatusBarPipelineFlags statusBarPipelineFlags,
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
WifiStatusTrackerFactory trackerFactory,
@@ -257,6 +263,7 @@
bgExecutor,
callbackHandler,
accessPointController,
+ statusBarPipelineFlags,
new DataUsageController(context),
new SubscriptionDefaults(),
deviceProvisionedController,
@@ -284,6 +291,7 @@
Executor bgExecutor,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
+ StatusBarPipelineFlags statusBarPipelineFlags,
DataUsageController dataUsageController,
SubscriptionDefaults defaultsHandler,
DeviceProvisionedController deviceProvisionedController,
@@ -305,6 +313,7 @@
mBgLooper = bgLooper;
mBgExecutor = bgExecutor;
mCallbackHandler = callbackHandler;
+ mStatusBarPipelineFlags = statusBarPipelineFlags;
mDataSaverController = new DataSaverControllerImpl(context);
mBroadcastDispatcher = broadcastDispatcher;
mMobileFactory = mobileFactory;
@@ -1330,7 +1339,7 @@
mWifiSignalController.notifyListeners();
}
String sims = args.getString("sims");
- if (sims != null) {
+ if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
List<SubscriptionInfo> subs = new ArrayList<>();
if (num != mMobileSignalControllers.size()) {
@@ -1353,7 +1362,7 @@
mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
}
String mobile = args.getString("mobile");
- if (mobile != null) {
+ if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
boolean show = mobile.equals("show");
String datatype = args.getString("datatype");
String slotString = args.getString("slot");
@@ -1438,7 +1447,7 @@
controller.notifyListeners();
}
String carrierNetworkChange = args.getString("carriernetworkchange");
- if (carrierNetworkChange != null) {
+ if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
boolean show = carrierNetworkChange.equals("show");
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index ed7f648..0eb0000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -74,8 +74,8 @@
@JvmDefault
fun requestTopRoundness(
@FloatRange(from = 0.0, to = 1.0) value: Float,
- animate: Boolean,
sourceType: SourceType,
+ animate: Boolean,
): Boolean {
val roundnessMap = roundableState.topRoundnessMap
val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -105,6 +105,30 @@
}
/**
+ * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the
+ * view is shown.
+ *
+ * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+ * origins require different roundness, for the same property, the maximum value will always be
+ * chosen.
+ *
+ * @param value a value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestTopRoundness(
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ sourceType: SourceType,
+ ): Boolean {
+ return requestTopRoundness(
+ value = value,
+ sourceType = sourceType,
+ animate = roundableState.targetView.isShown
+ )
+ }
+
+ /**
* Request the bottom roundness [value] for a specific [sourceType].
*
* The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
@@ -119,8 +143,8 @@
@JvmDefault
fun requestBottomRoundness(
@FloatRange(from = 0.0, to = 1.0) value: Float,
- animate: Boolean,
sourceType: SourceType,
+ animate: Boolean,
): Boolean {
val roundnessMap = roundableState.bottomRoundnessMap
val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -149,9 +173,101 @@
return false
}
+ /**
+ * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if
+ * the view is shown.
+ *
+ * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+ * origins require different roundness, for the same property, the maximum value will always be
+ * chosen.
+ *
+ * @param value value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestBottomRoundness(
+ @FloatRange(from = 0.0, to = 1.0) value: Float,
+ sourceType: SourceType,
+ ): Boolean {
+ return requestBottomRoundness(
+ value = value,
+ sourceType = sourceType,
+ animate = roundableState.targetView.isShown
+ )
+ }
+
+ /**
+ * Request the roundness [value] for a specific [sourceType].
+ *
+ * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+ * more origins require different roundness, for the same property, the maximum value will
+ * always be chosen.
+ *
+ * @param top top value between 0f and 1f.
+ * @param bottom bottom value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @param animate true if it should animate to that value.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestRoundness(
+ @FloatRange(from = 0.0, to = 1.0) top: Float,
+ @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+ sourceType: SourceType,
+ animate: Boolean,
+ ): Boolean {
+ val hasTopChanged =
+ requestTopRoundness(value = top, sourceType = sourceType, animate = animate)
+ val hasBottomChanged =
+ requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate)
+ return hasTopChanged || hasBottomChanged
+ }
+
+ /**
+ * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view
+ * is shown.
+ *
+ * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+ * more origins require different roundness, for the same property, the maximum value will
+ * always be chosen.
+ *
+ * @param top top value between 0f and 1f.
+ * @param bottom bottom value between 0f and 1f.
+ * @param sourceType the source from which the request for roundness comes.
+ * @return Whether the roundness was changed.
+ */
+ @JvmDefault
+ fun requestRoundness(
+ @FloatRange(from = 0.0, to = 1.0) top: Float,
+ @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+ sourceType: SourceType,
+ ): Boolean {
+ return requestRoundness(
+ top = top,
+ bottom = bottom,
+ sourceType = sourceType,
+ animate = roundableState.targetView.isShown,
+ )
+ }
+
+ /**
+ * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown.
+ *
+ * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+ * more origins require different roundness, for the same property, the maximum value will
+ * always be chosen.
+ *
+ * @param sourceType the source from which the request for roundness comes.
+ */
+ @JvmDefault
+ fun requestRoundnessReset(sourceType: SourceType) {
+ requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType)
+ }
+
/** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
@JvmDefault
- fun applyRoundness() {
+ fun applyRoundnessAndInvalidate() {
roundableState.targetView.invalidate()
}
@@ -227,7 +343,7 @@
/** Set the current top roundness */
internal fun setTopRoundness(
value: Float,
- animated: Boolean = targetView.isShown,
+ animated: Boolean,
) {
PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
}
@@ -235,11 +351,19 @@
/** Set the current bottom roundness */
internal fun setBottomRoundness(
value: Float,
- animated: Boolean = targetView.isShown,
+ animated: Boolean,
) {
PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
}
+ fun debugString() = buildString {
+ append("TargetView: ${targetView.hashCode()} ")
+ append("Top: $topRoundness ")
+ append(topRoundnessMap.map { "${it.key} ${it.value}" })
+ append(" Bottom: $bottomRoundness ")
+ append(bottomRoundnessMap.map { "${it.key} ${it.value}" })
+ }
+
companion object {
private val DURATION: AnimationProperties =
AnimationProperties()
@@ -252,7 +376,7 @@
override fun setValue(view: View, value: Float) {
roundable.roundableState.topRoundness = value
- roundable.applyRoundness()
+ roundable.applyRoundnessAndInvalidate()
}
},
R.id.top_roundess_animator_tag,
@@ -267,7 +391,7 @@
override fun setValue(view: View, value: Float) {
roundable.roundableState.bottomRoundness = value
- roundable.applyRoundness()
+ roundable.applyRoundnessAndInvalidate()
}
},
R.id.bottom_roundess_animator_tag,
@@ -277,7 +401,31 @@
}
}
-enum class SourceType {
+/**
+ * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a
+ * private property of a class.
+ */
+interface SourceType {
+ companion object {
+ /**
+ * This is the most convenient way to define a new [SourceType].
+ *
+ * For example:
+ *
+ * ```kotlin
+ * private val SECTION = SourceType.from("Section")
+ * ```
+ */
+ @JvmStatic
+ fun from(name: String) =
+ object : SourceType {
+ override fun toString() = name
+ }
+ }
+}
+
+@Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()"))
+enum class LegacySourceType : SourceType {
DefaultValue,
OnDismissAnimation,
OnScroll,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d29298a..fbe88df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -39,9 +39,13 @@
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
* to implement dimming/activating on Keyguard for the double-tap gesture
@@ -91,6 +95,7 @@
= new PathInterpolator(0.6f, 0, 0.5f, 1);
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
= new PathInterpolator(0, 0, 0.5f, 1);
+ private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>();
private int mTintedRippleColor;
private int mNormalRippleColor;
private Gefingerpoken mTouchHandler;
@@ -134,6 +139,7 @@
private boolean mDismissed;
private boolean mRefocusOnDismiss;
private AccessibilityManager mAccessibilityManager;
+ protected boolean mUseRoundnessSourceTypes;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -613,9 +619,9 @@
protected void resetAllContentAlphas() {}
@Override
- public void applyRoundness() {
- super.applyRoundness();
+ public void applyRoundnessAndInvalidate() {
applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
+ super.applyRoundnessAndInvalidate();
}
@Override
@@ -775,6 +781,33 @@
mAccessibilityManager = accessibilityManager;
}
+ /**
+ * Enable the support for rounded corner based on the SourceType
+ * @param enabled true if is supported
+ */
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) {
+ for (SourceType sourceType : mOnDetachResetRoundness) {
+ requestRoundnessReset(sourceType);
+ }
+ mOnDetachResetRoundness.clear();
+ }
+ }
+
+ /**
+ * SourceType which should be reset when this View is detached
+ * @param sourceType will be reset on View detached
+ */
+ public void addOnDetachResetRoundness(SourceType sourceType) {
+ mOnDetachResetRoundness.add(sourceType);
+ }
+
public interface OnActivatedListener {
void onActivated(ActivatableNotificationView view);
void onActivationReset(ActivatableNotificationView view);
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 d7d5ac9..c7c1634 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
@@ -91,6 +91,7 @@
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -143,6 +144,9 @@
private static final int MENU_VIEW_INDEX = 0;
public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+ private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+ private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+ private static final SourceType PINNED = SourceType.from("Pinned");
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
@@ -150,6 +154,7 @@
private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
private boolean mIsFaded;
+ private boolean mAnimatePinnedRoundness = false;
/**
* Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -376,7 +381,7 @@
private float mTopRoundnessDuringLaunchAnimation;
private float mBottomRoundnessDuringLaunchAnimation;
- private boolean mIsNotificationGroupCornerEnabled;
+ private float mSmallRoundness;
/**
* Returns whether the given {@code statusBarNotification} is a system notification.
@@ -844,7 +849,9 @@
}
onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
- row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ if (!mUseRoundnessSourceTypes) {
+ row.requestBottomRoundness(0.0f, LegacySourceType.DefaultValue, /* animate = */ false);
+ }
}
/**
@@ -860,7 +867,10 @@
if (child.keepInParentForDismissAnimation()) {
mChildrenContainer.removeNotification(child);
child.setIsChildInGroup(false, null);
- child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+ if (!mUseRoundnessSourceTypes) {
+ LegacySourceType sourceType = LegacySourceType.DefaultValue;
+ child.requestBottomRoundness(0f, sourceType, /* animate = */ false);
+ }
child.setKeepInParentForDismissAnimation(false);
logKeepInParentChildDetached(child);
childCountChanged = true;
@@ -915,6 +925,9 @@
mNotificationParent.updateBackgroundForGroupState();
}
updateBackgroundClipping();
+ if (mUseRoundnessSourceTypes) {
+ updateBaseRoundness();
+ }
}
@Override
@@ -1033,6 +1046,16 @@
if (isAboveShelf() != wasAboveShelf) {
mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
}
+ if (mUseRoundnessSourceTypes) {
+ if (pinned) {
+ // Should be animated if someone explicitly set it to 0 and the row is shown.
+ boolean animated = mAnimatePinnedRoundness && isShown();
+ requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
+ } else {
+ requestRoundnessReset(PINNED);
+ mAnimatePinnedRoundness = true;
+ }
+ }
}
@Override
@@ -1607,6 +1630,8 @@
super(context, attrs);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
+ float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
+ mSmallRoundness = radius / getMaxRadius();
initDimens();
}
@@ -1839,7 +1864,7 @@
mChildrenContainer.setIsLowPriority(mIsLowPriority);
mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
mChildrenContainer.onNotificationUpdated();
- mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+ mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
mTranslateableViews.add(mChildrenContainer);
});
@@ -2271,7 +2296,7 @@
@Override
public float getTopRoundness() {
- if (mExpandAnimationRunning) {
+ if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
return mTopRoundnessDuringLaunchAnimation;
}
@@ -2280,7 +2305,7 @@
@Override
public float getBottomRoundness() {
- if (mExpandAnimationRunning) {
+ if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
return mBottomRoundnessDuringLaunchAnimation;
}
@@ -3436,17 +3461,24 @@
}
@Override
- public void applyRoundness() {
- super.applyRoundness();
+ public void applyRoundnessAndInvalidate() {
applyChildrenRoundness();
+ super.applyRoundnessAndInvalidate();
}
private void applyChildrenRoundness() {
if (mIsSummaryWithChildren) {
- mChildrenContainer.requestBottomRoundness(
- getBottomRoundness(),
- /* animate = */ false,
- SourceType.DefaultValue);
+ if (mUseRoundnessSourceTypes) {
+ mChildrenContainer.requestRoundness(
+ /* top = */ getTopRoundness(),
+ /* bottom = */ getBottomRoundness(),
+ FROM_PARENT);
+ } else {
+ mChildrenContainer.requestBottomRoundness(
+ getBottomRoundness(),
+ LegacySourceType.DefaultValue,
+ /* animate = */ false);
+ }
}
}
@@ -3605,6 +3637,7 @@
} else {
pw.println("no viewState!!!");
}
+ pw.println("Roundness: " + getRoundableState().debugString());
if (mIsSummaryWithChildren) {
pw.println();
@@ -3649,14 +3682,38 @@
return mTargetPoint;
}
+ /** Update the minimum roundness based on current state */
+ private void updateBaseRoundness() {
+ if (isChildInGroup()) {
+ requestRoundnessReset(BASE_VALUE);
+ } else {
+ requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE);
+ }
+ }
+
/**
- * Enable the support for rounded corner in notification group
+ * Enable the support for rounded corner based on the SourceType
* @param enabled true if is supported
*/
- public void enableNotificationGroupCorner(boolean enabled) {
- mIsNotificationGroupCornerEnabled = enabled;
+ @Override
+ public void useRoundnessSourceTypes(boolean enabled) {
+ super.useRoundnessSourceTypes(enabled);
if (mChildrenContainer != null) {
- mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+ mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+ }
+ }
+
+ @Override
+ public String toString() {
+ String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+ return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }";
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mUseRoundnessSourceTypes) {
+ updateBaseRoundness();
}
}
}
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 8a400d5..d113860 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
@@ -255,8 +255,8 @@
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
});
- mView.enableNotificationGroupCorner(
- mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
+ mView.useRoundnessSourceTypes(
+ mFeatureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
}
private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2324627..0213b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -219,9 +219,9 @@
}
@Override
- public void applyRoundness() {
+ public void applyRoundnessAndInvalidate() {
invalidateOutline();
- super.applyRoundness();
+ super.applyRoundnessAndInvalidate();
}
protected void setBackgroundTop(int backgroundTop) {
@@ -233,7 +233,7 @@
public void onDensityOrFontScaleChanged() {
initDimens();
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
@Override
@@ -241,7 +241,7 @@
int previousHeight = getActualHeight();
super.setActualHeight(actualHeight, notifyListeners);
if (previousHeight != actualHeight) {
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -250,7 +250,7 @@
int previousAmount = getClipTopAmount();
super.setClipTopAmount(clipTopAmount);
if (previousAmount != clipTopAmount) {
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -259,14 +259,14 @@
int previousAmount = getClipBottomAmount();
super.setClipBottomAmount(clipBottomAmount);
if (previousAmount != clipBottomAmount) {
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
protected void setOutlineAlpha(float alpha) {
if (alpha != mOutlineAlpha) {
mOutlineAlpha = alpha;
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -280,7 +280,7 @@
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
}
@@ -340,7 +340,7 @@
// Outlines need to be at least 1 dp
mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
- applyRoundness();
+ applyRoundnessAndInvalidate();
}
public Path getCustomClipPath(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f13e48d..1f664cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -71,6 +71,8 @@
private View mFeedbackIcon;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
+ private boolean mUseRoundnessSourceTypes;
+ private RoundnessChangedListener mRoundnessChangedListener;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
@@ -117,6 +119,20 @@
return mRoundableState;
}
+ @Override
+ public void applyRoundnessAndInvalidate() {
+ if (mUseRoundnessSourceTypes && mRoundnessChangedListener != null) {
+ // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
+ // clip our canvas. So we should invalidate our parent.
+ mRoundnessChangedListener.applyRoundnessAndInvalidate();
+ }
+ Roundable.super.applyRoundnessAndInvalidate();
+ }
+
+ public void setOnRoundnessChangedListener(RoundnessChangedListener listener) {
+ mRoundnessChangedListener = listener;
+ }
+
protected void resolveHeaderViews() {
mIcon = mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -343,4 +359,23 @@
}
}
}
+
+ /**
+ * Enable the support for rounded corner based on the SourceType
+ *
+ * @param enabled true if is supported
+ */
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
+ /**
+ * Interface that handle the Roundness changes
+ */
+ public interface RoundnessChangedListener {
+ /**
+ * This method will be called when this class call applyRoundnessAndInvalidate()
+ */
+ void applyRoundnessAndInvalidate();
+ }
}
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 d43ca823..4a8e2db 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
@@ -47,6 +47,7 @@
import com.android.systemui.statusbar.NotificationGroupingUtil;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.Roundable;
@@ -83,6 +84,7 @@
return mAnimationFilter;
}
}.setDuration(200);
+ private static final SourceType FROM_PARENT = SourceType.from("FromParent(NCC)");
private final List<View> mDividers = new ArrayList<>();
private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
@@ -131,7 +133,7 @@
private int mUntruncatedChildCount;
private boolean mContainingNotificationIsFaded = false;
private RoundableState mRoundableState;
- private boolean mIsNotificationGroupCornerEnabled;
+ private boolean mUseRoundnessSourceTypes;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -313,9 +315,12 @@
row.setContentTransformationAmount(0, false /* isLastChild */);
row.setNotificationFaded(mContainingNotificationIsFaded);
- // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
- // Here we should reset the `OnScroll` roundness only on top-level rows.
- NotificationShelf.resetOnScrollRoundness(row);
+ if (!mUseRoundnessSourceTypes) {
+ // This is a workaround, the NotificationShelf should be the owner of `OnScroll`
+ // roundness.
+ // Here we should reset the `OnScroll` roundness only on top-level rows.
+ NotificationShelf.resetLegacyOnScrollRoundness(row);
+ }
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
@@ -323,6 +328,10 @@
viewState.cancelAnimations(row);
row.cancelAppearDrawing();
}
+
+ if (mUseRoundnessSourceTypes) {
+ applyRoundnessAndInvalidate();
+ }
}
private void ensureRemovedFromTransientContainer(View v) {
@@ -356,6 +365,11 @@
if (!row.isRemoved()) {
mGroupingUtil.restoreChildNotification(row);
}
+
+ if (mUseRoundnessSourceTypes) {
+ row.requestRoundnessReset(FROM_PARENT);
+ applyRoundnessAndInvalidate();
+ }
}
/**
@@ -382,6 +396,10 @@
getContext(),
mNotificationHeader,
mContainingNotification);
+ mNotificationHeaderWrapper.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+ if (mUseRoundnessSourceTypes) {
+ mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+ }
addView(mNotificationHeader, 0);
invalidate();
} else {
@@ -419,6 +437,12 @@
getContext(),
mNotificationHeaderLowPriority,
mContainingNotification);
+ mNotificationHeaderWrapperLowPriority.useRoundnessSourceTypes(
+ mUseRoundnessSourceTypes
+ );
+ if (mUseRoundnessSourceTypes) {
+ mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+ }
addView(mNotificationHeaderLowPriority, 0);
invalidate();
} else {
@@ -841,7 +865,7 @@
isCanvasChanged = true;
canvas.save();
- if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+ if (mUseRoundnessSourceTypes && translation != 0f) {
clipPath.offset(translation, 0f);
canvas.clipPath(clipPath);
clipPath.offset(-translation, 0f);
@@ -1392,24 +1416,28 @@
}
@Override
- public void applyRoundness() {
- Roundable.super.applyRoundness();
+ public void applyRoundnessAndInvalidate() {
boolean last = true;
for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) {
continue;
}
- child.requestTopRoundness(
- /* value = */ 0f,
- /* animate = */ isShown(),
- SourceType.DefaultValue);
- child.requestBottomRoundness(
- /* value = */ last ? getBottomRoundness() : 0f,
- /* animate = */ isShown(),
- SourceType.DefaultValue);
+ if (mUseRoundnessSourceTypes) {
+ child.requestRoundness(
+ /* top = */ 0f,
+ /* bottom = */ last ? getBottomRoundness() : 0f,
+ FROM_PARENT);
+ } else {
+ child.requestRoundness(
+ /* top = */ 0f,
+ /* bottom = */ last ? getBottomRoundness() : 0f,
+ LegacySourceType.DefaultValue,
+ /* animate = */ isShown());
+ }
last = false;
}
+ Roundable.super.applyRoundnessAndInvalidate();
}
public void setHeaderVisibleAmount(float headerVisibleAmount) {
@@ -1467,10 +1495,17 @@
}
/**
- * Enable the support for rounded corner in notification group
+ * Enable the support for rounded corner based on the SourceType
+ *
* @param enabled true if is supported
*/
- public void enableNotificationGroupCorner(boolean enabled) {
- mIsNotificationGroupCornerEnabled = enabled;
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
+ @Override
+ public String toString() {
+ String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+ return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }";
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 6810055..fde8c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -25,6 +25,9 @@
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.SourceType;
@@ -45,6 +48,7 @@
public class NotificationRoundnessManager implements Dumpable {
private static final String TAG = "NotificationRoundnessManager";
+ private static final SourceType DISMISS_ANIMATION = SourceType.from("DismissAnimation");
private final ExpandableView[] mFirstInSectionViews;
private final ExpandableView[] mLastInSectionViews;
@@ -63,12 +67,14 @@
private ExpandableView mSwipedView = null;
private Roundable mViewBeforeSwipedView = null;
private Roundable mViewAfterSwipedView = null;
+ private boolean mUseRoundnessSourceTypes;
@Inject
NotificationRoundnessManager(
NotificationSectionsFeatureManager sectionsFeatureManager,
NotificationRoundnessLogger notifLogger,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ FeatureFlags featureFlags) {
int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
mFirstInSectionViews = new ExpandableView[numberOfSections];
mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -76,6 +82,7 @@
mTmpLastInSectionViews = new ExpandableView[numberOfSections];
mNotifLogger = notifLogger;
mDumpManager = dumpManager;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mDumpManager.registerDumpable(TAG, this);
}
@@ -94,6 +101,7 @@
}
public void updateView(ExpandableView view, boolean animate) {
+ if (mUseRoundnessSourceTypes) return;
boolean changed = updateViewWithoutCallback(view, animate);
if (changed) {
mRoundingChangedCallback.run();
@@ -110,6 +118,7 @@
boolean updateViewWithoutCallback(
ExpandableView view,
boolean animate) {
+ if (mUseRoundnessSourceTypes) return false;
if (view == null
|| view == mViewBeforeSwipedView
|| view == mViewAfterSwipedView) {
@@ -118,13 +127,13 @@
final boolean isTopChanged = view.requestTopRoundness(
getRoundnessDefaultValue(view, true /* top */),
- animate,
- SourceType.DefaultValue);
+ LegacySourceType.DefaultValue,
+ animate);
final boolean isBottomChanged = view.requestBottomRoundness(
getRoundnessDefaultValue(view, /* top = */ false),
- animate,
- SourceType.DefaultValue);
+ LegacySourceType.DefaultValue,
+ animate);
final boolean isFirstInSection = isFirstInSection(view);
final boolean isLastInSection = isLastInSection(view);
@@ -139,6 +148,7 @@
}
private boolean isFirstInSection(ExpandableView view) {
+ if (mUseRoundnessSourceTypes) return false;
for (int i = 0; i < mFirstInSectionViews.length; i++) {
if (view == mFirstInSectionViews[i]) {
return true;
@@ -148,6 +158,7 @@
}
private boolean isLastInSection(ExpandableView view) {
+ if (mUseRoundnessSourceTypes) return false;
for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
if (view == mLastInSectionViews[i]) {
return true;
@@ -160,9 +171,6 @@
Roundable viewBefore,
ExpandableView viewSwiped,
Roundable viewAfter) {
- final boolean animate = true;
- final SourceType source = SourceType.OnDismissAnimation;
-
// This method requires you to change the roundness of the current View targets and reset
// the roundness of the old View targets (if any) to 0f.
// To avoid conflicts, it generates a set of old Views and removes the current Views
@@ -172,31 +180,34 @@
if (mSwipedView != null) oldViews.add(mSwipedView);
if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
+ final SourceType source;
+ if (mUseRoundnessSourceTypes) {
+ source = DISMISS_ANIMATION;
+ } else {
+ source = LegacySourceType.OnDismissAnimation;
+ }
+
mViewBeforeSwipedView = viewBefore;
if (viewBefore != null) {
oldViews.remove(viewBefore);
- viewBefore.requestTopRoundness(0f, animate, source);
- viewBefore.requestBottomRoundness(1f, animate, source);
+ viewBefore.requestRoundness(/* top = */ 0f, /* bottom = */ 1f, source);
}
mSwipedView = viewSwiped;
if (viewSwiped != null) {
oldViews.remove(viewSwiped);
- viewSwiped.requestTopRoundness(1f, animate, source);
- viewSwiped.requestBottomRoundness(1f, animate, source);
+ viewSwiped.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, source);
}
mViewAfterSwipedView = viewAfter;
if (viewAfter != null) {
oldViews.remove(viewAfter);
- viewAfter.requestTopRoundness(1f, animate, source);
- viewAfter.requestBottomRoundness(0f, animate, source);
+ viewAfter.requestRoundness(/* top = */ 1f, /* bottom = */ 0f, source);
}
// After setting the current Views, reset the views that are still present in the set.
for (Roundable oldView : oldViews) {
- oldView.requestTopRoundness(0f, animate, source);
- oldView.requestBottomRoundness(0f, animate, source);
+ oldView.requestRoundnessReset(source);
}
}
@@ -204,7 +215,23 @@
mIsClearAllInProgress = isClearingAll;
}
+ /**
+ * Check if "Clear all" notifications is in progress.
+ */
+ public boolean isClearAllInProgress() {
+ return mIsClearAllInProgress;
+ }
+
+ /**
+ * Check if we can request the `Pulsing` roundness for notification.
+ */
+ public boolean shouldRoundNotificationPulsing() {
+ return mRoundForPulsingViews;
+ }
+
private float getRoundnessDefaultValue(Roundable view, boolean top) {
+ if (mUseRoundnessSourceTypes) return 0f;
+
if (view == null) {
return 0f;
}
@@ -250,6 +277,7 @@
}
public void setExpanded(float expandedHeight, float appearFraction) {
+ if (mUseRoundnessSourceTypes) return;
mExpanded = expandedHeight != 0.0f;
mAppearFraction = appearFraction;
if (mTrackedHeadsUp != null) {
@@ -258,6 +286,7 @@
}
public void updateRoundedChildren(NotificationSection[] sections) {
+ if (mUseRoundnessSourceTypes) return;
boolean anyChanged = false;
for (int i = 0; i < sections.length; i++) {
mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
@@ -280,6 +309,7 @@
NotificationSection[] sections,
ExpandableView[] oldViews,
boolean first) {
+ if (mUseRoundnessSourceTypes) return false;
boolean anyChanged = false;
for (ExpandableView oldView : oldViews) {
if (oldView != null) {
@@ -313,6 +343,7 @@
NotificationSection[] sections,
ExpandableView[] oldViews,
boolean first) {
+ if (mUseRoundnessSourceTypes) return false;
boolean anyChanged = false;
for (NotificationSection section : sections) {
ExpandableView newView =
@@ -339,6 +370,15 @@
mAnimatedChildren = animatedChildren;
}
+ /**
+ * Check if the view should be animated
+ * @param view target view
+ * @return true, if is in the AnimatedChildren set
+ */
+ public boolean isAnimatedChild(ExpandableView view) {
+ return mAnimatedChildren.contains(view);
+ }
+
public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
mRoundingChangedCallback = roundingChangedCallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index a1b77ac..070b439 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,8 +19,11 @@
import android.util.Log
import android.view.View
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
import com.android.systemui.statusbar.notification.dagger.AlertingHeader
@@ -44,12 +47,16 @@
private val keyguardMediaController: KeyguardMediaController,
private val sectionsFeatureManager: NotificationSectionsFeatureManager,
private val mediaContainerController: MediaContainerController,
+ private val notificationRoundnessManager: NotificationRoundnessManager,
@IncomingHeader private val incomingHeaderController: SectionHeaderController,
@PeopleHeader private val peopleHeaderController: SectionHeaderController,
@AlertingHeader private val alertingHeaderController: SectionHeaderController,
- @SilentHeader private val silentHeaderController: SectionHeaderController
+ @SilentHeader private val silentHeaderController: SectionHeaderController,
+ featureFlags: FeatureFlags
) : SectionProvider {
+ private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
+
private val configurationListener = object : ConfigurationController.ConfigurationListener {
override fun onLocaleListChanged() {
reinflateViews()
@@ -177,11 +184,49 @@
size = sections.size,
operation = SectionBounds::addNotif
)
+
+ // Build a set of the old first/last Views of the sections
+ val oldFirstChildren = sections.mapNotNull { it.firstVisibleChild }.toSet().toMutableSet()
+ val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet()
+
// Update each section with the associated boundary, tracking if there was a change
val changed = sections.fold(false) { changed, section ->
val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
- bounds.updateSection(section) || changed
+ val isSectionChanged = bounds.updateSection(section)
+ isSectionChanged || changed
}
+
+ if (useRoundnessSourceTypes) {
+ val newFirstChildren = sections.mapNotNull { it.firstVisibleChild }
+ val newLastChildren = sections.mapNotNull { it.lastVisibleChild }
+
+ // Update the roundness of Views that weren't already in the first/last position
+ newFirstChildren.forEach { firstChild ->
+ val wasFirstChild = oldFirstChildren.remove(firstChild)
+ if (!wasFirstChild) {
+ val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(firstChild)
+ val animated = firstChild.isShown && notAnimatedChild
+ firstChild.requestTopRoundness(1f, SECTION, animated)
+ }
+ }
+ newLastChildren.forEach { lastChild ->
+ val wasLastChild = oldLastChildren.remove(lastChild)
+ if (!wasLastChild) {
+ val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(lastChild)
+ val animated = lastChild.isShown && notAnimatedChild
+ lastChild.requestBottomRoundness(1f, SECTION, animated)
+ }
+ }
+
+ // The Views left in the set are no longer in the first/last position
+ oldFirstChildren.forEach { noMoreFirstChild ->
+ noMoreFirstChild.requestTopRoundness(0f, SECTION)
+ }
+ oldLastChildren.forEach { noMoreLastChild ->
+ noMoreLastChild.requestBottomRoundness(0f, SECTION)
+ }
+ }
+
if (DEBUG) {
logSections(sections)
}
@@ -215,5 +260,6 @@
companion object {
private const val TAG = "NotifSectionsManager"
private const val DEBUG = false
+ private val SECTION = SourceType.from("Section")
}
}
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 7c3e52c..21e2bd8 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
@@ -191,10 +191,13 @@
private final boolean mDebugLines;
private Paint mDebugPaint;
- /** Used to track the Y positions that were already used to draw debug text labels. */
+ /**
+ * 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 final boolean mUseRoundnessSourceTypes;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -355,15 +358,14 @@
private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
private boolean mContinuousBackgroundUpdate;
- private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
- = () -> {
- updateViewShadows();
- return true;
- };
+ private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
+ updateViewShadows();
+ return true;
+ };
private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
- updateBackground();
- return true;
- };
+ updateBackground();
+ return true;
+ };
private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
float endY = view.getTranslationY() + view.getActualHeight();
float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -419,7 +421,8 @@
*/
private int mMaxDisplayedNotifications = -1;
private float mKeyguardBottomPadding = -1;
- @VisibleForTesting int mStatusBarHeight;
+ @VisibleForTesting
+ int mStatusBarHeight;
private int mMinInteractionHeight;
private final Rect mClipRect = new Rect();
private boolean mIsClipped;
@@ -568,6 +571,8 @@
@Nullable
private OnClickListener mManageButtonClickListener;
+ @Nullable
+ private OnNotificationRemovedListener mOnNotificationRemovedListener;
public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0, 0);
@@ -576,6 +581,7 @@
mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -741,7 +747,7 @@
}
private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
- boolean expected, boolean actual) {
+ boolean expected, boolean actual) {
if (mLogger == null) return;
mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
}
@@ -868,13 +874,13 @@
/**
* Draws round rects for each background section.
- *
+ * <p>
* We want to draw a round rect for each background section as defined by {@link #mSections}.
* However, if two sections are directly adjacent with no gap between them (e.g. on the
* lockscreen where the shelf can appear directly below the high priority section, or while
* scrolling the shade so that the top of the shelf is right at the bottom of the high priority
* section), we don't want to round the adjacent corners.
- *
+ * <p>
* Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
* need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
* This method tracks the top of each rect we need to draw, then iterates through the visible
@@ -883,7 +889,7 @@
* the current section. When we're done iterating we will always have one rect left to draw.
*/
private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
- int animationYOffset) {
+ int animationYOffset) {
int backgroundRectTop = top;
int lastSectionBottom =
mSections[0].getCurrentBounds().bottom + animationYOffset;
@@ -974,7 +980,7 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
void initView(Context context, NotificationSwipeHelper swipeHelper,
- NotificationStackSizeCalculator notificationStackSizeCalculator) {
+ NotificationStackSizeCalculator notificationStackSizeCalculator) {
mScroller = new OverScroller(getContext());
mSwipeHelper = swipeHelper;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -1304,18 +1310,19 @@
/**
* @return Whether we should skip stack height updates.
* True when
- * 1) Unlock hint is running
- * 2) Swiping up on lockscreen or flinging down after swipe up
+ * 1) Unlock hint is running
+ * 2) Swiping up on lockscreen or flinging down after swipe up
*/
private boolean shouldSkipHeightUpdate() {
return mAmbientState.isOnKeyguard()
&& (mAmbientState.isUnlockHintRunning()
- || mAmbientState.isSwipingUp()
- || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+ || mAmbientState.isSwipingUp()
+ || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
}
/**
* Apply expansion fraction to the y position and height of the notifications panel.
+ *
* @param listenerNeedsAnimation does the listener need to animate?
*/
private void updateStackPosition(boolean listenerNeedsAnimation) {
@@ -1708,7 +1715,7 @@
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
ExpandableView getChildAtPosition(float touchX, float touchY,
- boolean requireMinHeight, boolean ignoreDecors) {
+ boolean requireMinHeight, boolean ignoreDecors) {
// find the view under the pointer, accounting for GONE views
final int count = getChildCount();
for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -1868,9 +1875,9 @@
public void dismissViewAnimated(
View child, Consumer<Boolean> endRunnable, int delay, long duration) {
if (child instanceof SectionHeaderView) {
- ((StackScrollerDecorView) child).setContentVisible(
- false /* visible */, true /* animate */, endRunnable);
- return;
+ ((StackScrollerDecorView) child).setContentVisible(
+ false /* visible */, true /* animate */, endRunnable);
+ return;
}
mSwipeHelper.dismissChild(
child,
@@ -2032,10 +2039,11 @@
/**
* Scrolls by the given delta, overscrolling if needed. If called during a fling and the delta
* would cause us to exceed the provided maximum overscroll, springs back instead.
- *
+ * <p>
* This method performs the determination of whether we're exceeding the overscroll and clamps
* the scroll amount if so. The actual scrolling/overscrolling happens in
* {@link #onCustomOverScrolled(int, boolean)}
+ *
* @param deltaY The (signed) number of pixels to scroll.
* @param scrollY The current scroll position (absolute scrolling only).
* @param scrollRangeY The maximum allowable scroll position (absolute scrolling only).
@@ -2098,7 +2106,7 @@
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators) {
+ boolean cancelAnimators) {
setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
}
@@ -2114,7 +2122,7 @@
*/
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators, boolean isRubberbanded) {
+ boolean cancelAnimators, boolean isRubberbanded) {
if (cancelAnimators) {
mStateAnimator.cancelOverScrollAnimators(onTop);
}
@@ -2123,7 +2131,7 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
- boolean isRubberbanded) {
+ boolean isRubberbanded) {
amount = Math.max(0, amount);
if (animate) {
mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
@@ -2179,7 +2187,7 @@
* Scrolls to the given position, overscrolling if needed. If called during a fling and the
* position exceeds the provided maximum overscroll, springs back instead.
*
- * @param scrollY The target scroll position.
+ * @param scrollY The target scroll position.
* @param clampedY Whether this value was clamped by the calling method, meaning we've reached
* the overscroll limit.
*/
@@ -2288,8 +2296,8 @@
if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
List<ExpandableNotificationRow> notificationChildren =
row.getAttachedChildren();
- for (int childIndex = 0; childIndex < notificationChildren.size();
- childIndex++) {
+ int childrenSize = notificationChildren.size();
+ for (int childIndex = 0; childIndex < childrenSize; childIndex++) {
ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
if (rowChild.getTranslationY() + rowTranslation >= translationY) {
return rowChild;
@@ -2365,10 +2373,9 @@
/**
* Calculate the gap height between two different views
*
- * @param previous the previousView
- * @param current the currentView
+ * @param previous the previousView
+ * @param current the currentView
* @param visibleIndex the visible index in the list
- *
* @return the gap height needed before the current view
*/
public float calculateGapHeight(
@@ -2376,7 +2383,7 @@
ExpandableView current,
int visibleIndex
) {
- return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
+ return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard());
}
@@ -2642,15 +2649,15 @@
return mScrolledToTopOnFirstDown
&& !mExpandedInThisMotion
&& (initialVelocity > mMinimumVelocity
- || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
+ || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
}
/**
* Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
* account.
*
- * @param qsHeight the top padding imposed by the quick settings panel
- * @param animate whether to animate the change
+ * @param qsHeight the top padding imposed by the quick settings panel
+ * @param animate whether to animate the change
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public void updateTopPadding(float qsHeight, boolean animate) {
@@ -2724,21 +2731,34 @@
}
-
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setChildTransferInProgress(boolean childTransferInProgress) {
Assert.isMainThread();
mChildTransferInProgress = childTransferInProgress;
}
+ /**
+ * Set the remove notification listener
+ * @param listener callback for notification removed
+ */
+ public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
+ mOnNotificationRemovedListener = listener;
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
// we only call our internal methods if this is actually a removal and not just a
// notification which becomes a child notification
+ ExpandableView expandableView = (ExpandableView) child;
if (!mChildTransferInProgress) {
- onViewRemovedInternal((ExpandableView) child, this);
+ onViewRemovedInternal(expandableView, this);
+ }
+ if (mOnNotificationRemovedListener != null) {
+ mOnNotificationRemovedListener.onNotificationRemoved(
+ expandableView,
+ mChildTransferInProgress);
}
}
@@ -2998,8 +3018,10 @@
mAnimateNextSectionBoundsChange = false;
}
mAmbientState.setLastVisibleBackgroundChild(lastChild);
- // TODO: Refactor SectionManager and put the RoundnessManager there.
- mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+ if (!mUseRoundnessSourceTypes) {
+ // TODO: Refactor SectionManager and put the RoundnessManager there.
+ mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+ }
mAnimateBottomOnLayout = false;
invalidate();
}
@@ -3206,7 +3228,7 @@
// Only animate if we still have pinned heads up, otherwise we just have the
// regular collapse animation of the lock screen
|| (mKeyguardBypassEnabled && onKeyguard()
- && mInHeadsUpPinnedMode);
+ && mInHeadsUpPinnedMode);
if (performDisappearAnimation && !isHeadsUp) {
type = row.wasJustClicked()
? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
@@ -3351,7 +3373,7 @@
AnimationEvent animEvent = duration == null
? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)
: new AnimationEvent(
- child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
+ child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
mAnimationEvents.add(animEvent);
}
mChildrenChangingPositions.clear();
@@ -3442,7 +3464,9 @@
@ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
- return new StackScrollAlgorithm(context, this);
+ StackScrollAlgorithm stackScrollAlgorithm = new StackScrollAlgorithm(context, this);
+ stackScrollAlgorithm.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+ return stackScrollAlgorithm;
}
/**
@@ -3772,7 +3796,7 @@
}
private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
- int statusBarState, boolean touchIsClick) {
+ int statusBarState, boolean touchIsClick) {
if (mLogger == null) {
return;
}
@@ -3957,7 +3981,9 @@
mOnEmptySpaceClickListener = listener;
}
- /** @hide */
+ /**
+ * @hide
+ */
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
@@ -4728,7 +4754,9 @@
return touchY > mTopPadding + mStackTranslation;
}
- /** @hide */
+ /**
+ * @hide
+ */
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -5353,7 +5381,9 @@
return canChildBeCleared(row) && matchesSelection(row, selection);
}
- /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
+ /**
+ * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
+ */
public void setManageButtonClickListener(@Nullable OnClickListener listener) {
mManageButtonClickListener = listener;
if (mFooterView != null) {
@@ -5418,6 +5448,7 @@
/**
* Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
* notification positions accordingly.
+ *
* @param height the new wake up height
* @return the overflow how much the height is further than he lowest notification
*/
@@ -5649,7 +5680,7 @@
* Set rounded rect clipping bounds on this view.
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
- int bottomRadius) {
+ int bottomRadius) {
if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
&& mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
&& mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
@@ -5710,7 +5741,7 @@
mLaunchingNotification = launching;
mLaunchingNotificationNeedsToBeClipped = mLaunchAnimationParams != null
&& (mLaunchAnimationParams.getStartRoundedTopClipping() > 0
- || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
+ || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification) {
mLaunchedNotificationClipPath.reset();
}
@@ -5748,7 +5779,7 @@
mLaunchAnimationParams.getProgress(0,
NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
- mLaunchAnimationParams.getTop(), expandProgress),
+ mLaunchAnimationParams.getTop(), expandProgress),
mRoundedRectClippingTop);
float topRadius = mLaunchAnimationParams.getTopCornerRadius();
float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius();
@@ -5872,25 +5903,25 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public interface OnOverscrollTopChangedListener {
- /**
- * Notifies a listener that the overscroll has changed.
- *
- * @param amount the amount of overscroll, in pixels
- * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
- * unrubberbanded motion to directly expand overscroll view (e.g
- * expand
- * QS)
- */
- void onOverscrollTopChanged(float amount, boolean isRubberbanded);
+ /**
+ * Notifies a listener that the overscroll has changed.
+ *
+ * @param amount the amount of overscroll, in pixels
+ * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
+ * unrubberbanded motion to directly expand overscroll view (e.g
+ * expand
+ * QS)
+ */
+ void onOverscrollTopChanged(float amount, boolean isRubberbanded);
- /**
- * Notify a listener that the scroller wants to escape from the scrolling motion and
- * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
- *
- * @param velocity The velocity that the Scroller had when over flinging
- * @param open Should the fling open or close the overscroll view.
- */
- void flingTopOverscroll(float velocity, boolean open);
+ /**
+ * Notify a listener that the scroller wants to escape from the scrolling motion and
+ * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
+ *
+ * @param velocity The velocity that the Scroller had when over flinging
+ * @param open Should the fling open or close the overscroll view.
+ */
+ void flingTopOverscroll(float velocity, boolean open);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6252,7 +6283,9 @@
}
};
- public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
+ public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+ return mHeadsUpCallback;
+ }
void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) {
boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
@@ -6357,15 +6390,25 @@
return mLastSentExpandedHeight;
}
- /** Enum for selecting some or all notification rows (does not included non-notif views). */
+ /**
+ * Enum for selecting some or all notification rows (does not included non-notif views).
+ */
@Retention(SOURCE)
@IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
- @interface SelectedRows {}
- /** All rows representing notifs. */
+ @interface SelectedRows {
+ }
+
+ /**
+ * All rows representing notifs.
+ */
public static final int ROWS_ALL = 0;
- /** Only rows where entry.isHighPriority() is true. */
+ /**
+ * Only rows where entry.isHighPriority() is true.
+ */
public static final int ROWS_HIGH_PRIORITY = 1;
- /** Only rows where entry.isHighPriority() is false. */
+ /**
+ * Only rows where entry.isHighPriority() is false.
+ */
public static final int ROWS_GENTLE = 2;
interface ClearAllListener {
@@ -6380,4 +6423,16 @@
void onAnimationEnd(
List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
}
+
+ /**
+ *
+ */
+ public interface OnNotificationRemovedListener {
+ /**
+ *
+ * @param child
+ * @param isTransferInProgress
+ */
+ void onNotificationRemoved(ExpandableView child, boolean isTransferInProgress);
+ }
}
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 e3336b2..4bcc0b6 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
@@ -182,10 +182,12 @@
private NotificationStackScrollLayout mView;
private boolean mFadeNotificationsOnDismiss;
private NotificationSwipeHelper mSwipeHelper;
- @Nullable private Boolean mHistoryEnabled;
+ @Nullable
+ private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private final FeatureFlags mFeatureFlags;
+ private final boolean mUseRoundnessSourceTypes;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private View mLongPressedView;
@@ -391,7 +393,7 @@
if (item != null) {
Point origin = provider.getRevealAnimationOrigin();
mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
- } else {
+ } else {
Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
+ "menu item in menuItemtoExposeOnSnap. Skipping.");
}
@@ -420,7 +422,7 @@
@Override
public void onSnooze(StatusBarNotification sbn,
- NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+ NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
}
@@ -544,7 +546,7 @@
@Override
public boolean updateSwipeProgress(View animView, boolean dismissable,
- float swipeProgress) {
+ float swipeProgress) {
// Returning true prevents alpha fading.
return !mFadeNotificationsOnDismiss;
}
@@ -584,16 +586,22 @@
@Override
public void onHeadsUpPinned(NotificationEntry entry) {
- mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
+ if (!mUseRoundnessSourceTypes) {
+ mNotificationRoundnessManager.updateView(
+ entry.getRow(),
+ /* animate = */ false);
+ }
}
@Override
public void onHeadsUpUnPinned(NotificationEntry entry) {
- ExpandableNotificationRow row = entry.getRow();
- // update the roundedness posted, because we might be animating away the
- // headsup soon, so no need to set the roundedness to 0 and then back to 1.
- row.post(() -> mNotificationRoundnessManager.updateView(row,
- true /* animate */));
+ if (!mUseRoundnessSourceTypes) {
+ ExpandableNotificationRow row = entry.getRow();
+ // update the roundedness posted, because we might be animating away the
+ // headsup soon, so no need to set the roundedness to 0 and then back to 1.
+ row.post(() -> mNotificationRoundnessManager.updateView(row,
+ true /* animate */));
+ }
}
@Override
@@ -603,8 +611,10 @@
mView.setNumHeadsUp(numEntries);
mView.setTopHeadsUpEntry(topEntry);
generateHeadsUpAnimation(entry, isHeadsUp);
- ExpandableNotificationRow row = entry.getRow();
- mNotificationRoundnessManager.updateView(row, true /* animate */);
+ if (!mUseRoundnessSourceTypes) {
+ ExpandableNotificationRow row = entry.getRow();
+ mNotificationRoundnessManager.updateView(row, true /* animate */);
+ }
}
};
@@ -697,6 +707,7 @@
mSeenNotificationsProvider = seenNotificationsProvider;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mNotificationTargetsHelper = notificationTargetsHelper;
updateResources();
}
@@ -763,8 +774,10 @@
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
- mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
- mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ if (!mUseRoundnessSourceTypes) {
+ mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+ mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ }
mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
@@ -983,7 +996,7 @@
}
public boolean isAddOrRemoveAnimationPending() {
- return mView.isAddOrRemoveAnimationPending();
+ return mView != null && mView.isAddOrRemoveAnimationPending();
}
public int getVisibleNotificationCount() {
@@ -997,9 +1010,11 @@
Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
return false;
}
- mHistoryEnabled = historyEnabled =
- Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
- Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
+ mView.getContext().getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+ 0,
+ UserHandle.USER_CURRENT) == 1;
}
return historyEnabled;
}
@@ -1029,7 +1044,7 @@
}
public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
- boolean cancelAnimators) {
+ boolean cancelAnimators) {
mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
}
@@ -1140,7 +1155,9 @@
}
public void setAlpha(float alpha) {
- mView.setAlpha(alpha);
+ if (mView != null) {
+ mView.setAlpha(alpha);
+ }
}
public float calculateAppearFraction(float height) {
@@ -1205,7 +1222,7 @@
/**
* Update whether we should show the empty shade view ("no notifications" in the shade).
- *
+ * <p>
* When in split mode, notifications are always visible regardless of the state of the
* QuickSettings panel. That being the case, empty view is always shown if the other conditions
* are true.
@@ -1231,7 +1248,7 @@
/**
* @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
- * and false otherwise.
+ * and false otherwise.
*/
private boolean isInTransitionToKeyguard() {
final int currentState = mStatusBarStateController.getState();
@@ -1263,7 +1280,9 @@
mView.setExpandedHeight(expandedHeight);
}
- /** Sets the QS header. Used to check if a touch is within its bounds. */
+ /**
+ * Sets the QS header. Used to check if a touch is within its bounds.
+ */
public void setQsHeader(ViewGroup view) {
mView.setQsHeader(view);
}
@@ -1326,7 +1345,7 @@
public RemoteInputController.Delegate createDelegate() {
return new RemoteInputController.Delegate() {
public void setRemoteInputActive(NotificationEntry entry,
- boolean remoteInputActive) {
+ boolean remoteInputActive) {
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
updateFooter();
@@ -1457,7 +1476,7 @@
}
private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
- @SelectedRows int selectedRows) {
+ @SelectedRows int selectedRows) {
if (selectedRows == ROWS_ALL) {
mNotifCollection.dismissAllNotifications(
mLockscreenUserManager.getCurrentUserId());
@@ -1500,8 +1519,8 @@
/**
* @return the inset during the full shade transition, that needs to be added to the position
- * of the quick settings edge. This is relevant for media, that is transitioning
- * from the keyguard host to the quick settings one.
+ * of the quick settings edge. This is relevant for media, that is transitioning
+ * from the keyguard host to the quick settings one.
*/
public int getFullShadeTransitionInset() {
MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
@@ -1515,10 +1534,10 @@
/**
* @param fraction The fraction of lockscreen to shade transition.
* 0f for all other states.
- *
- * Once the lockscreen to shade transition completes and the shade is 100% open,
- * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
- * until the next lockscreen-to-shade transition.
+ * <p>
+ * Once the lockscreen to shade transition completes and the shade is 100% open,
+ * LockscreenShadeTransitionController resets amount and fraction to 0, where
+ * they remain until the next lockscreen-to-shade transition.
*/
public void setTransitionToFullShadeAmount(float fraction) {
mView.setFractionToShade(fraction);
@@ -1531,7 +1550,9 @@
mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
}
- /** */
+ /**
+ *
+ */
public void setWillExpand(boolean willExpand) {
mView.setWillExpand(willExpand);
}
@@ -1547,7 +1568,7 @@
* Set rounded rect clipping bounds on this view.
*/
public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
- int bottomRadius) {
+ int bottomRadius) {
mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
}
@@ -1567,6 +1588,15 @@
}
/**
+ * Set the remove notification listener
+ * @param listener callback for notification removed
+ */
+ public void setOnNotificationRemovedListener(
+ NotificationStackScrollLayout.OnNotificationRemovedListener listener) {
+ mView.setOnNotificationRemovedListener(listener);
+ }
+
+ /**
* Enum for UiEvent logged from this class
*/
enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@@ -1576,10 +1606,13 @@
@UiEvent(doc = "User dismissed all silent notifications from notification panel.")
DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
private final int mId;
+
NotificationPanelEvent(int id) {
mId = id;
}
- @Override public int getId() {
+
+ @Override
+ public int getId() {
return mId;
}
@@ -1720,8 +1753,12 @@
@Override
public void bindRow(ExpandableNotificationRow row) {
row.setHeadsUpAnimatingAwayListener(animatingAway -> {
- mNotificationRoundnessManager.updateView(row, false);
- mHeadsUpAppearanceController.updateHeader(row.getEntry());
+ if (!mUseRoundnessSourceTypes) {
+ mNotificationRoundnessManager.updateView(row, false);
+ }
+ NotificationEntry entry = row.getEntry();
+ mHeadsUpAppearanceController.updateHeader(entry);
+ mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(entry);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index ee57411..aaf9300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -20,6 +20,7 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -34,9 +35,11 @@
import com.android.systemui.SwipeHelper;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -49,6 +52,7 @@
@VisibleForTesting
protected static final long COVER_MENU_DELAY = 4000;
private static final String TAG = "NotificationSwipeHelper";
+ private static final SourceType SWIPE_DISMISS = SourceType.from("SwipeDismiss");
private final Runnable mFalsingCheck;
private View mTranslatingParentView;
private View mMenuExposedView;
@@ -64,13 +68,21 @@
private WeakReference<NotificationMenuRowPlugin> mCurrMenuRowRef;
private boolean mIsExpanded;
private boolean mPulsing;
+ private final NotificationRoundnessManager mNotificationRoundnessManager;
+ private final boolean mUseRoundnessSourceTypes;
NotificationSwipeHelper(
- Resources resources, ViewConfiguration viewConfiguration,
- FalsingManager falsingManager, FeatureFlags featureFlags,
- int swipeDirection, NotificationCallback callback,
- NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
+ Resources resources,
+ ViewConfiguration viewConfiguration,
+ FalsingManager falsingManager,
+ FeatureFlags featureFlags,
+ int swipeDirection,
+ NotificationCallback callback,
+ NotificationMenuRowPlugin.OnMenuEventListener menuListener,
+ NotificationRoundnessManager notificationRoundnessManager) {
super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+ mNotificationRoundnessManager = notificationRoundnessManager;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mMenuListener = menuListener;
mCallback = callback;
mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
@@ -304,6 +316,33 @@
handleMenuCoveredOrDismissed();
}
+ @Override
+ protected void prepareDismissAnimation(View view, Animator anim) {
+ super.prepareDismissAnimation(view, anim);
+
+ if (mUseRoundnessSourceTypes
+ && view instanceof ExpandableNotificationRow
+ && mNotificationRoundnessManager.isClearAllInProgress()) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, SWIPE_DISMISS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ row.requestRoundnessReset(SWIPE_DISMISS);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ row.requestRoundnessReset(SWIPE_DISMISS);
+ }
+ });
+ }
+ }
+
@VisibleForTesting
protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
super.dismissChild(view, velocity, useAccelerateInterpolator);
@@ -521,14 +560,17 @@
private int mSwipeDirection;
private NotificationCallback mNotificationCallback;
private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+ private NotificationRoundnessManager mNotificationRoundnessManager;
@Inject
Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
- FalsingManager falsingManager, FeatureFlags featureFlags) {
+ FalsingManager falsingManager, FeatureFlags featureFlags,
+ NotificationRoundnessManager notificationRoundnessManager) {
mResources = resources;
mViewConfiguration = viewConfiguration;
mFalsingManager = falsingManager;
mFeatureFlags = featureFlags;
+ mNotificationRoundnessManager = notificationRoundnessManager;
}
Builder setSwipeDirection(int swipeDirection) {
@@ -549,7 +591,8 @@
NotificationSwipeHelper build() {
return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
- mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener);
+ mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+ mNotificationRoundnessManager);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
index 991a14b..548d1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -20,8 +20,7 @@
constructor(
featureFlags: FeatureFlags,
) {
- private val isNotificationGroupCornerEnabled =
- featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+ private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
/**
* This method looks for views that can be rounded (and implement [Roundable]) during a
@@ -48,7 +47,7 @@
if (notificationParent != null && childrenContainer != null) {
// We are inside a notification group
- if (!isNotificationGroupCornerEnabled) {
+ if (!useRoundnessSourceTypes) {
return RoundableTargets(null, null, null)
}
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 d8c6878..aff7b4c 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
@@ -31,6 +31,7 @@
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +52,7 @@
private static final String TAG = "StackScrollAlgorithm";
private static final Boolean DEBUG = false;
+ private static final SourceType STACK_SCROLL_ALGO = SourceType.from("StackScrollAlgorithm");
private final ViewGroup mHostView;
private float mPaddingBetweenElements;
@@ -70,6 +72,7 @@
private float mQuickQsOffsetHeight;
private float mSmallCornerRadius;
private float mLargeCornerRadius;
+ private boolean mUseRoundnessSourceTypes;
public StackScrollAlgorithm(
Context context,
@@ -129,7 +132,7 @@
}
private void updateAlphaState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
for (ExpandableView view : algorithmState.visibleChildren) {
final ViewState viewState = view.getViewState();
@@ -229,7 +232,7 @@
}
private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView v = algorithmState.visibleChildren.get(i);
@@ -241,7 +244,7 @@
}
private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
- int speedBumpIndex) {
+ int speedBumpIndex) {
int childCount = algorithmState.visibleChildren.size();
int belowSpeedBump = speedBumpIndex;
for (int i = 0; i < childCount; i++) {
@@ -268,7 +271,7 @@
}
private void updateClipping(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
float drawStart = ambientState.isOnKeyguard() ? 0
: ambientState.getStackY() - ambientState.getScrollY();
float clipStart = 0;
@@ -314,7 +317,7 @@
* Updates the dimmed, activated and hiding sensitive states of the children.
*/
private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
- StackScrollAlgorithmState algorithmState) {
+ StackScrollAlgorithmState algorithmState) {
boolean dimmed = ambientState.isDimmed();
boolean hideSensitive = ambientState.isHideSensitive();
View activatedChild = ambientState.getActivatedChild();
@@ -408,7 +411,7 @@
}
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
- ExpandableView v) {
+ ExpandableView v) {
ExpandableViewState viewState = v.getViewState();
viewState.notGoneIndex = notGoneIndex;
state.visibleChildren.add(v);
@@ -434,7 +437,7 @@
* @param ambientState The current ambient state
*/
protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
if (!ambientState.isOnKeyguard()
|| (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -448,7 +451,7 @@
}
private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
- int i) {
+ int i) {
expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
if (currentYPosition <= 0) {
expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -496,9 +499,13 @@
}
@VisibleForTesting
- void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
- boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
-
+ void maybeUpdateHeadsUpIsVisible(
+ ExpandableViewState viewState,
+ boolean isShadeExpanded,
+ boolean mustStayOnScreen,
+ boolean topVisible,
+ float viewEnd,
+ float hunMax) {
if (isShadeExpanded && mustStayOnScreen && topVisible) {
viewState.headsUpIsVisible = viewEnd < hunMax;
}
@@ -676,7 +683,7 @@
}
private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
@@ -693,7 +700,7 @@
}
private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
// Move the tracked heads up into position during the appear animation, by interpolating
@@ -777,7 +784,7 @@
*/
@VisibleForTesting
void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
- ExpandableViewState viewState) {
+ ExpandableViewState viewState) {
final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
viewState.getYTranslation());
@@ -792,7 +799,7 @@
// Pin HUN to bottom of expanded QS
// while the rest of notifications are scrolled offscreen.
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
- ExpandableViewState childState) {
+ ExpandableViewState childState) {
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
@@ -807,14 +814,19 @@
// Animate pinned HUN bottom corners to and from original roundness.
final float originalCornerRadius =
row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
- final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+ final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
- row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
+ if (mUseRoundnessSourceTypes) {
+ row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO);
+ row.addOnDetachResetRoundness(STACK_SCROLL_ALGO);
+ } else {
+ row.requestBottomRoundness(bottomValue, LegacySourceType.OnScroll);
+ }
}
@VisibleForTesting
float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
- float viewMaxHeight, float originalCornerRadius) {
+ float viewMaxHeight, float originalCornerRadius) {
// Compute y where corner roundness should be in its original unpinned state.
// We use view max height because the pinned collapsed HUN expands to max height
@@ -844,7 +856,7 @@
* @param ambientState The ambient state of the algorithm
*/
private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
+ AmbientState ambientState) {
int childCount = algorithmState.visibleChildren.size();
float childrenOnTop = 0.0f;
@@ -876,9 +888,9 @@
* previous HUNs whose Z positions are greater than 0.
*/
protected float updateChildZValue(int i, float childrenOnTop,
- StackScrollAlgorithmState algorithmState,
- AmbientState ambientState,
- boolean isTopHun) {
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState,
+ boolean isTopHun) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
float baseZ = ambientState.getBaseZHeight();
@@ -950,6 +962,14 @@
this.mIsExpanded = isExpanded;
}
+ /**
+ * Enable the support for rounded corner based on the SourceType
+ * @param enabled true if is supported
+ */
+ public void useRoundnessSourceTypes(boolean enabled) {
+ mUseRoundnessSourceTypes = enabled;
+ }
+
public static class StackScrollAlgorithmState {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 484441a..c217ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -19,11 +19,14 @@
import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
import android.graphics.Rect;
+import android.util.MathUtils;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -32,8 +35,10 @@
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
import com.android.systemui.statusbar.policy.Clock;
@@ -51,6 +56,7 @@
/**
* Controls the appearance of heads up notifications in the icon area and the header itself.
+ * It also controls the roundness of the heads up notifications and the pulsing notifications.
*/
@StatusBarFragmentScope
public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
@@ -59,12 +65,17 @@
NotificationWakeUpCoordinator.WakeUpListener {
public static final int CONTENT_FADE_DURATION = 110;
public static final int CONTENT_FADE_DELAY = 100;
+
+ private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
+ private static final SourceType PULSING = SourceType.from("Pulsing");
private final NotificationIconAreaController mNotificationIconAreaController;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationStackScrollLayoutController mStackScrollerController;
private final DarkIconDispatcher mDarkIconDispatcher;
private final NotificationPanelViewController mNotificationPanelViewController;
+ private final NotificationRoundnessManager mNotificationRoundnessManager;
+ private final boolean mUseRoundnessSourceTypes;
private final Consumer<ExpandableNotificationRow>
mSetTrackingHeadsUp = this::setTrackingHeadsUp;
private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
@@ -105,11 +116,15 @@
CommandQueue commandQueue,
NotificationStackScrollLayoutController stackScrollerController,
NotificationPanelViewController notificationPanelViewController,
+ NotificationRoundnessManager notificationRoundnessManager,
+ FeatureFlags featureFlags,
HeadsUpStatusBarView headsUpStatusBarView,
Clock clockView,
@Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
super(headsUpStatusBarView);
mNotificationIconAreaController = notificationIconAreaController;
+ mNotificationRoundnessManager = notificationRoundnessManager;
+ mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mHeadsUpManager = headsUpManager;
// We may be mid-HUN-expansion when this controller is re-created (for example, if the user
@@ -179,6 +194,7 @@
public void onHeadsUpPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
}
private void updateTopEntry() {
@@ -316,6 +332,7 @@
public void onHeadsUpUnPinned(NotificationEntry entry) {
updateTopEntry();
updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
}
public void setAppearFraction(float expandedHeight, float appearFraction) {
@@ -346,7 +363,9 @@
ExpandableNotificationRow previousTracked = mTrackedChild;
mTrackedChild = trackedChild;
if (previousTracked != null) {
- updateHeader(previousTracked.getEntry());
+ NotificationEntry entry = previousTracked.getEntry();
+ updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
}
}
@@ -357,6 +376,7 @@
private void updateHeadsUpHeaders() {
mHeadsUpManager.getAllEntries().forEach(entry -> {
updateHeader(entry);
+ updateHeadsUpAndPulsingRoundness(entry);
});
}
@@ -370,6 +390,31 @@
row.setHeaderVisibleAmount(headerVisibleAmount);
}
+ /**
+ * Update the HeadsUp and the Pulsing roundness based on current state
+ * @param entry target notification
+ */
+ public void updateHeadsUpAndPulsingRoundness(NotificationEntry entry) {
+ if (mUseRoundnessSourceTypes) {
+ ExpandableNotificationRow row = entry.getRow();
+ boolean isTrackedChild = row == mTrackedChild;
+ if (row.isPinned() || row.isHeadsUpAnimatingAway() || isTrackedChild) {
+ float roundness = MathUtils.saturate(1f - mAppearFraction);
+ row.requestRoundness(roundness, roundness, HEADS_UP);
+ } else {
+ row.requestRoundnessReset(HEADS_UP);
+ }
+ if (mNotificationRoundnessManager.shouldRoundNotificationPulsing()) {
+ if (row.showingPulsing()) {
+ row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PULSING);
+ } else {
+ row.requestRoundnessReset(PULSING);
+ }
+ }
+ }
+ }
+
+
@Override
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
mView.onDarkChanged(areas, darkIntensity, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index fb67f1a..e7afc50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.dagger
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
@@ -29,6 +30,7 @@
import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
@@ -40,6 +42,8 @@
import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
abstract class StatusBarPipelineModule {
@@ -52,8 +56,7 @@
@Binds
abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
- @Binds
- abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+ @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
@Binds
abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
@@ -63,15 +66,18 @@
impl: MobileConnectionsRepositoryImpl
): MobileConnectionsRepository
- @Binds
- abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+ @Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
- @Binds
- abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+ @Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
@Binds
abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
+ @Binds
+ @IntoMap
+ @ClassKey(MobileUiAdapter::class)
+ abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index da87f73..5479b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,6 +20,7 @@
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
/** Internal enum representation of the telephony data connection states */
@@ -28,6 +29,7 @@
Connecting(DATA_CONNECTING),
Disconnected(DATA_DISCONNECTED),
Disconnecting(DATA_DISCONNECTING),
+ Unknown(DATA_UNKNOWN),
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -36,5 +38,6 @@
DATA_CONNECTING -> DataConnectionState.Connecting
DATA_DISCONNECTED -> DataConnectionState.Disconnected
DATA_DISCONNECTING -> DataConnectionState.Disconnecting
- else -> throw IllegalArgumentException("unknown data state received")
+ DATA_UNKNOWN -> DataConnectionState.Unknown
+ else -> throw IllegalArgumentException("unknown data state received $this")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index c7e0ce1..d9487bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
@@ -29,9 +30,10 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* This class is intended to provide a context to collect on the
@@ -50,9 +52,9 @@
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
- @Application scope: CoroutineScope,
+ @Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
-) {
+) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
interactor.filteredSubscriptions.mapLatest { infos ->
infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
@@ -66,18 +68,19 @@
* NOTE: this should go away as the view presenter learns more about this data pipeline
*/
private val mobileSubIdsState: StateFlow<List<Int>> =
- mobileSubIds
- .onEach {
- // Only notify the icon controller if we want to *render* the new icons.
- // Note that this flow may still run if
- // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
- // get the logging data without rendering.
- if (statusBarPipelineFlags.useNewMobileIcons()) {
- // Notify the icon controller here so that it knows to add icons
- iconController.setNewMobileIconSubIds(it)
- }
+ mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ override fun start() {
+ // Only notify the icon controller if we want to *render* the new icons.
+ // Note that this flow may still run if
+ // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+ // get the logging data without rendering.
+ if (statusBarPipelineFlags.useNewMobileIcons()) {
+ scope.launch {
+ mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
}
- .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ }
+ }
/**
* Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index b816364..5223760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -24,6 +24,7 @@
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import javax.inject.Inject
@@ -73,7 +74,9 @@
// Note that this flow may still run if
// [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
// want to get the logging data without rendering.
- if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+ if (
+ wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
+ ) {
iconController.setNewWifiIcon()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 345f8cb..f5b5950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -92,8 +93,10 @@
launch {
viewModel.wifiIcon.collect { wifiIcon ->
- view.isVisible = wifiIcon != null
- wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
+ view.isVisible = wifiIcon is WifiIcon.Visible
+ if (wifiIcon is WifiIcon.Visible) {
+ IconViewBinder.bind(wifiIcon.icon, iconView)
+ }
}
}
@@ -135,7 +138,7 @@
return object : Binding {
override fun getShouldIconBeVisible(): Boolean {
- return viewModel.wifiIcon.value != null
+ return viewModel.wifiIcon.value is WifiIcon.Visible
}
override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
new file mode 100644
index 0000000..e491d2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.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.statusbar.pipeline.wifi.ui.model
+
+import android.annotation.DrawableRes
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** Represents the various states of the wifi icon. */
+sealed interface WifiIcon : Diffable<WifiIcon> {
+ /** Represents a wifi icon that should be hidden (not visible). */
+ object Hidden : WifiIcon {
+ override fun toString() = "hidden"
+ }
+
+ /**
+ * Represents a visible wifi icon that uses [res] as its image and [contentDescription] as its
+ * description.
+ */
+ class Visible(
+ @DrawableRes res: Int,
+ val contentDescription: ContentDescription.Loaded,
+ ) : WifiIcon {
+ val icon = Icon.Resource(res, contentDescription)
+
+ override fun toString() = contentDescription.description.toString()
+ }
+
+ override fun logDiffs(prevVal: WifiIcon, row: TableRowLogger) {
+ if (prevVal.toString() != toString()) {
+ row.logChange(COL_ICON, toString())
+ }
+ }
+
+ override fun logFull(row: TableRowLogger) {
+ row.logChange(COL_ICON, toString())
+ }
+}
+
+private const val COL_ICON = "wifiIcon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 95ab251..a29c9b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
@@ -28,7 +28,7 @@
*/
class HomeWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
- wifiIcon: StateFlow<Icon.Resource?>,
+ wifiIcon: StateFlow<WifiIcon>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 86535d6..1e190fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -17,15 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** A view model for the wifi icon shown on keyguard (lockscreen). */
class KeyguardWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
- wifiIcon: StateFlow<Icon.Resource?>,
+ wifiIcon: StateFlow<WifiIcon>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index 7cbdf5d..e35a8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
@@ -33,8 +33,8 @@
statusBarPipelineFlags: StatusBarPipelineFlags,
debugTint: Int,
- /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
- val wifiIcon: StateFlow<Icon.Resource?>,
+ /** The wifi icon that should be displayed. */
+ val wifiIcon: StateFlow<WifiIcon>,
/** True if the activity in view should be visible. */
val isActivityInViewVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index fd54c5f..18e62b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -17,15 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
class QsWifiViewModel(
statusBarPipelineFlags: StatusBarPipelineFlags,
- wifiIcon: StateFlow<Icon.Resource?>,
+ wifiIcon: StateFlow<WifiIcon>,
isActivityInViewVisible: Flow<Boolean>,
isActivityOutViewVisible: Flow<Boolean>,
isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 0782bbb..ec7ba65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,16 +17,18 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
-import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.R
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 com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -71,50 +73,39 @@
connectivityConstants: ConnectivityConstants,
private val context: Context,
logger: ConnectivityPipelineLogger,
+ @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
interactor: WifiInteractor,
@Application private val scope: CoroutineScope,
statusBarPipelineFlags: StatusBarPipelineFlags,
wifiConstants: WifiConstants,
) {
- /**
- * Returns the drawable resource ID to use for the wifi icon based on the given network.
- * Null if we can't compute the icon.
- */
- @DrawableRes
- private fun WifiNetworkModel.iconResId(): Int? {
+ /** Returns the icon to use based on the given network. */
+ private fun WifiNetworkModel.icon(): WifiIcon {
return when (this) {
- is WifiNetworkModel.CarrierMerged -> null
- is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
- is WifiNetworkModel.Active ->
- when {
- this.level == null -> null
- this.isValidated -> WIFI_FULL_ICONS[this.level]
- else -> WIFI_NO_INTERNET_ICONS[this.level]
- }
- }
- }
-
- /**
- * Returns the content description for the wifi icon based on the given network.
- * Null if we can't compute the content description.
- */
- private fun WifiNetworkModel.contentDescription(): ContentDescription? {
- return when (this) {
- is WifiNetworkModel.CarrierMerged -> null
- is WifiNetworkModel.Inactive ->
+ is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
+ is WifiNetworkModel.Inactive -> WifiIcon.Visible(
+ res = WIFI_NO_NETWORK,
ContentDescription.Loaded(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
+ )
is WifiNetworkModel.Active ->
when (this.level) {
- null -> null
+ null -> WifiIcon.Hidden
else -> {
val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
- this.isValidated -> ContentDescription.Loaded(levelDesc)
+ this.isValidated ->
+ WifiIcon.Visible(
+ WIFI_FULL_ICONS[this.level],
+ ContentDescription.Loaded(levelDesc)
+ )
else ->
- ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
+ WifiIcon.Visible(
+ WIFI_NO_INTERNET_ICONS[this.level],
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ )
)
}
}
@@ -122,8 +113,8 @@
}
}
- /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
- private val wifiIcon: StateFlow<Icon.Resource?> =
+ /** The wifi icon that should be displayed. */
+ private val wifiIcon: StateFlow<WifiIcon> =
combine(
interactor.isEnabled,
interactor.isDefault,
@@ -131,22 +122,29 @@
interactor.wifiNetwork,
) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
- return@combine null
+ return@combine WifiIcon.Hidden
}
- val iconResId = wifiNetwork.iconResId() ?: return@combine null
- val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+ val icon = wifiNetwork.icon()
return@combine when {
isDefault -> icon
wifiConstants.alwaysShowIconIfEnabled -> icon
!connectivityConstants.hasDataCapabilities -> icon
wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
- else -> null
+ else -> WifiIcon.Hidden
}
}
- .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
- .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+ .logDiffsForTable(
+ wifiTableLogBuffer,
+ columnPrefix = "",
+ initialValue = WifiIcon.Hidden,
+ )
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = WifiIcon.Hidden
+ )
/** The wifi activity status. Null if we shouldn't display the activity status. */
private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a9d05d1..ea40208 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -105,8 +105,9 @@
*
* This method handles inflating and attaching the view, then delegates to [updateView] to
* display the correct information in the view.
+ * @param onViewTimeout a runnable that runs after the view timeout.
*/
- fun displayView(newInfo: T) {
+ fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
val currentDisplayInfo = displayInfo
// Update our list of active devices by removing it if necessary, then adding back at the
@@ -173,7 +174,10 @@
cancelViewTimeout?.run()
}
cancelViewTimeout = mainExecutor.executeDelayed(
- { removeView(id, REMOVAL_REASON_TIMEOUT) },
+ {
+ removeView(id, REMOVAL_REASON_TIMEOUT)
+ onViewTimeout?.run()
+ },
timeout.toLong()
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index ae30ca0..162c915 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -22,6 +22,8 @@
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.hardware.display.DisplayManager
import android.hardware.input.InputManager
+import android.os.Handler
+import android.os.Looper
import android.os.Trace
import android.view.Choreographer
import android.view.Display
@@ -32,6 +34,7 @@
import android.view.SurfaceSession
import android.view.WindowManager
import android.view.WindowlessWindowManager
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.statusbar.LightRevealEffect
@@ -40,6 +43,7 @@
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.Assert.isMainThread
import com.android.systemui.util.traceSection
import com.android.wm.shell.displayareahelper.DisplayAreaHelper
import java.util.Optional
@@ -59,6 +63,7 @@
private val displayAreaHelper: Optional<DisplayAreaHelper>,
@Main private val executor: Executor,
@UiBackground private val backgroundExecutor: Executor,
+ @Background private val bgHandler: Handler,
private val rotationChangeProvider: RotationChangeProvider,
) {
@@ -120,11 +125,11 @@
try {
// Add the view only if we are unfolding and this is the first screen on
if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
- addView(onOverlayReady)
+ executeInBackground { addView(onOverlayReady) }
isUnfoldHandled = true
} else {
// No unfold transition, immediately report that overlay is ready
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
onOverlayReady.run()
}
} finally {
@@ -139,6 +144,7 @@
return
}
+ ensureInBackground()
ensureOverlayRemoved()
val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
@@ -152,7 +158,7 @@
val params = getLayoutParams()
newRoot.setView(newView, params)
- onOverlayReady?.let { callback ->
+ if (onOverlayReady != null) {
Trace.beginAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
newRoot.relayout(params) { transaction ->
@@ -170,7 +176,7 @@
.setFrameTimelineVsync(vsyncId + 1)
.addTransactionCommittedListener(backgroundExecutor) {
Trace.endAsyncSection("UnfoldLightRevealOverlayAnimation#relayout", 0)
- callback.run()
+ onOverlayReady.run()
}
.apply()
}
@@ -213,9 +219,12 @@
}
private fun ensureOverlayRemoved() {
- root?.release()
- root = null
- scrimView = null
+ ensureInBackground()
+ traceSection("ensureOverlayRemoved") {
+ root?.release()
+ root = null
+ scrimView = null
+ }
}
private fun getUnfoldedDisplayInfo(): DisplayInfo =
@@ -228,17 +237,17 @@
private inner class TransitionListener : TransitionProgressListener {
override fun onTransitionProgress(progress: Float) {
- scrimView?.revealAmount = progress
+ executeInBackground { scrimView?.revealAmount = progress }
}
override fun onTransitionFinished() {
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
}
override fun onTransitionStarted() {
// Add view for folding case (when unfolding the view is added earlier)
if (scrimView == null) {
- addView()
+ executeInBackground { addView() }
}
// Disable input dispatching during transition.
InputManager.getInstance().cancelCurrentTouch()
@@ -250,19 +259,35 @@
traceSection("UnfoldLightRevealOverlayAnimation#onRotationChanged") {
if (currentRotation != newRotation) {
currentRotation = newRotation
- scrimView?.revealEffect = createLightRevealEffect()
- root?.relayout(getLayoutParams())
+ executeInBackground {
+ scrimView?.revealEffect = createLightRevealEffect()
+ root?.relayout(getLayoutParams())
+ }
}
}
}
}
+ private fun executeInBackground(f: () -> Unit) {
+ ensureInMainThread()
+ // The UiBackground executor is not used as it doesn't have a prepared looper.
+ bgHandler.post(f)
+ }
+
+ private fun ensureInBackground() {
+ check(Looper.myLooper() == bgHandler.looper) { "Not being executed in the background!" }
+ }
+
+ private fun ensureInMainThread() {
+ isMainThread()
+ }
+
private inner class FoldListener :
FoldStateListener(
context,
Consumer { isFolded ->
if (isFolded) {
- ensureOverlayRemoved()
+ executeInBackground { ensureOverlayRemoved() }
isUnfoldHandled = false
}
this.isFolded = isFolded
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 0f3eddf..bff6132d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -50,13 +50,6 @@
}
/**
- * Wrapped version of {@link DeviceConfig#enforceReadPermission}.
- */
- public void enforceReadPermission(String namespace) {
- DeviceConfig.enforceReadPermission(namespace);
- }
-
- /**
* Wrapped version of {@link DeviceConfig#getBoolean}.
*/
public boolean getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 33c00fb..9349966 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -432,6 +432,11 @@
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+ & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
}
return changed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 833a6a4..1bc0d08 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1771,6 +1771,7 @@
if (ss.level == row.requestedLevel) {
row.requestedLevel = -1;
}
+ final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1815,8 +1816,12 @@
} else if (isRingSilent || zenMuted) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
- iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
- : R.drawable.ic_volume_media_bt;
+ if (isVoiceCallStream) {
+ iconRes = R.drawable.ic_volume_bt_sco;
+ } else {
+ iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt;
+ }
} else if (isStreamMuted(ss)) {
iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02738d5..8ef98f0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -253,6 +253,12 @@
splitScreen.onFinishedWakingUp();
}
});
+ mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+ @Override
+ public void goToFullscreenFromSplit() {
+ splitScreen.goToFullscreenFromSplit();
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index ffd95f4..d20be56 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -19,6 +19,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
@@ -43,6 +44,8 @@
@Mock
private lateinit var keyguardPasswordView: KeyguardPasswordView
@Mock
+ private lateinit var passwordEntry: EditText
+ @Mock
lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock
lateinit var securityMode: KeyguardSecurityModel.SecurityMode
@@ -81,6 +84,9 @@
).thenReturn(mKeyguardMessageArea)
Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
.thenReturn(mKeyguardMessageAreaController)
+ Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+ Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry)
+ ).thenReturn(passwordEntry)
keyguardPasswordViewController = KeyguardPasswordViewController(
keyguardPasswordView,
keyguardUpdateMonitor,
@@ -103,7 +109,10 @@
Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
- keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).requestFocus()
+ verify(keyguardPasswordView).showKeyboard()
+ }
}
@Test
@@ -115,6 +124,15 @@
}
@Test
+ fun testHideKeyboardWhenOnPause() {
+ keyguardPasswordViewController.onPause()
+ keyguardPasswordView.post {
+ verify(keyguardPasswordView).clearFocus()
+ verify(keyguardPasswordView).hideKeyboard()
+ }
+ }
+
+ @Test
fun startAppearAnimation() {
keyguardPasswordViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
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 d17e374..798839d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -34,6 +35,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.os.PowerManager;
@@ -41,6 +43,11 @@
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationTarget;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -52,21 +59,27 @@
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
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.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
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.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -80,8 +93,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -96,11 +107,15 @@
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock DumpManager mDumpManager;
+ private @Mock WindowManager mWindowManager;
+ private @Mock IActivityManager mActivityManager;
+ private @Mock ConfigurationController mConfigurationController;
private @Mock PowerManager mPowerManager;
private @Mock TrustManager mTrustManager;
private @Mock UserSwitcherController mUserSwitcherController;
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
+ private @Mock KeyguardBypassController mKeyguardBypassController;
private @Mock DozeParameters mDozeParameters;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
@@ -110,10 +125,13 @@
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
private @Mock ShadeController mShadeController;
- private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+ private NotificationShadeWindowController mNotificationShadeWindowController;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
private @Mock ScrimController mScrimController;
+ private @Mock SysuiColorExtractor mColorExtractor;
+ private @Mock AuthController mAuthController;
+ private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -130,6 +148,14 @@
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
+ final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
+ when(testViewRoot.getView()).thenReturn(mock(View.class));
+ when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
+ mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
+ mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
+ mConfigurationController, mViewMediator, mKeyguardBypassController,
+ mColorExtractor, mDumpManager, mKeyguardStateController,
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
createAndStartViewMediator();
}
@@ -287,6 +313,23 @@
verify(mCentralSurfaces).updateIsKeyguard();
}
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+ RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+
+ mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+ null, callback);
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -315,7 +358,7 @@
mInteractionJankMonitor,
mDreamOverlayStateController,
() -> mShadeController,
- mNotificationShadeWindowControllerLazy,
+ () -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
() -> mScrimController);
mViewMediator.start();
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 761773b..fdef344 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
@@ -702,7 +702,7 @@
}
@Test
- fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToGone() {
+ fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToInvisible() {
useRealConstraintSets()
val state = mediaData.copy(semanticActions = MediaButton())
@@ -711,7 +711,7 @@
player.bindPlayer(state, PACKAGE)
- assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
}
@Test
@@ -741,7 +741,7 @@
}
@Test
- fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToGone() {
+ fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToInvisible() {
useRealConstraintSets()
val state = mediaData.copy(semanticActions = MediaButton())
@@ -752,7 +752,7 @@
getEnabledChangeListener().onEnabledChanged(enabled = false)
- assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4437394..311740e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -265,6 +265,8 @@
@Test
fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+ displayReceiverTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -278,13 +280,15 @@
.isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
}
@Test
fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -297,6 +301,7 @@
@Test
fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -313,6 +318,7 @@
@Test
fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
var undoCallbackCalled = false
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -325,8 +331,9 @@
getChipbarView().getUndoButton().performClick()
- // Event index 1 since initially displaying the succeeded chip would also log an event
- assertThat(uiEventLoggerFake.eventId(1))
+ // Event index 2 since initially displaying the triggered and succeeded chip would also log
+ // events.
+ assertThat(uiEventLoggerFake.eventId(2))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
assertThat(undoCallbackCalled).isTrue()
assertThat(getChipbarView().getChipText())
@@ -335,6 +342,8 @@
@Test
fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+ displayThisDeviceTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -348,13 +357,15 @@
.isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
}
@Test
fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -367,6 +378,7 @@
@Test
fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -383,6 +395,7 @@
@Test
fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
var undoCallbackCalled = false
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -395,8 +408,9 @@
getChipbarView().getUndoButton().performClick()
- // Event index 1 since initially displaying the succeeded chip would also log an event
- assertThat(uiEventLoggerFake.eventId(1))
+ // Event index 2 since initially displaying the triggered and succeeded chip would also log
+ // events.
+ assertThat(uiEventLoggerFake.eventId(2))
.isEqualTo(
MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
)
@@ -407,6 +421,8 @@
@Test
fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+ displayReceiverTriggered()
+ reset(vibratorHelper)
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
routeInfo,
@@ -421,7 +437,8 @@
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@@ -429,6 +446,12 @@
@Test
fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ reset(vibratorHelper)
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
routeInfo,
null
@@ -442,7 +465,8 @@
assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
- assertThat(uiEventLoggerFake.eventId(0))
+ // Event index 1 since initially displaying the triggered chip would also log an event.
+ assertThat(uiEventLoggerFake.eventId(1))
.isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
verify(vibratorHelper).vibrate(any<VibrationEffect>())
}
@@ -517,6 +541,166 @@
}
@Test
+ fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+ displayReceiverTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+ displayThisDeviceTriggered()
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
+ fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+ routeInfo,
+ null
+ )
+ verify(windowManager).addView(any(), any())
+ reset(windowManager)
+
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+ routeInfo,
+ null
+ )
+
+ verify(logger).logInvalidStateTransitionError(any(), any())
+ verify(windowManager, never()).addView(any(), any())
+ }
+
+ @Test
fun receivesNewStateFromCommandQueue_isLogged() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
@@ -575,6 +759,7 @@
@Test
fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -598,6 +783,7 @@
@Test
fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -621,6 +807,7 @@
@Test
fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ displayReceiverTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
routeInfo,
@@ -660,6 +847,7 @@
@Test
fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+ displayThisDeviceTriggered()
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
routeInfo,
@@ -717,6 +905,26 @@
private fun ChipStateSender.getExpectedStateText(): String? {
return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
}
+
+ // display receiver triggered state helper method to make sure we start from a valid state
+ // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
+ private fun displayReceiverTriggered() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+ routeInfo,
+ null
+ )
+ }
+
+ // display this device triggered state helper method to make sure we start from a valid state
+ // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
+ private fun displayThisDeviceTriggered() {
+ commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+ StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+ routeInfo,
+ null
+ )
+ }
}
private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 9758842..4a9c750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,16 +16,21 @@
package com.android.systemui.notetask
import android.app.KeyguardManager
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.UserManager
import android.test.suitebuilder.annotation.SmallTest
-import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import org.junit.Before
import org.junit.Test
@@ -48,6 +53,7 @@
private val notesIntent = Intent(NOTES_ACTION)
@Mock lateinit var context: Context
+ @Mock lateinit var packageManager: PackageManager
@Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var optionalBubbles: Optional<Bubbles>
@@ -60,6 +66,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(context.packageManager).thenReturn(packageManager)
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
@@ -78,89 +85,125 @@
)
}
+ // region showNoteTask
@Test
- fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+ fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() {
+ fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(bubbles).showAppBubble(notesIntent)
verify(context, never()).startActivity(notesIntent)
}
@Test
- fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+ fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
- verify(context, never()).startActivity(notesIntent)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+
+ verify(context).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_bubblesIsNull_shouldDoNothing() {
+ fun showNoteTask_bubblesIsNull_shouldDoNothing() {
whenever(optionalBubbles.orElse(null)).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+ fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+ fun showNoteTask_userManagerIsNull_shouldDoNothing() {
whenever(optionalUserManager.orElse(null)).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+ fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_flagDisabled_shouldDoNothing() {
- createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ fun showNoteTask_flagDisabled_shouldDoNothing() {
+ createNoteTaskController(isEnabled = false).showNoteTask()
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
@Test
- fun handleSystemKey_userIsLocked_shouldDoNothing() {
+ fun showNoteTask_userIsLocked_shouldDoNothing() {
whenever(userManager.isUserUnlocked).thenReturn(false)
- createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+ createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
verify(context, never()).startActivity(notesIntent)
verify(bubbles, never()).showAppBubble(notesIntent)
}
+ // endregion
+
+ // region setNoteTaskShortcutEnabled
+ @Test
+ fun setNoteTaskShortcutEnabled_setTrue() {
+ createNoteTaskController().setNoteTaskShortcutEnabled(value = true)
+
+ val argument = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ argument.capture(),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+ assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+ }
+
+ @Test
+ fun setNoteTaskShortcutEnabled_setFalse() {
+ createNoteTaskController().setNoteTaskShortcutEnabled(value = false)
+
+ val argument = argumentCaptor<ComponentName>()
+ verify(context.packageManager)
+ .setComponentEnabledSetting(
+ argument.capture(),
+ eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+ eq(PackageManager.DONT_KILL_APP),
+ )
+ val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+ assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+ }
+ // endregion
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 334089c..538131a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -16,10 +16,10 @@
package com.android.systemui.notetask
import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.bubbles.Bubbles
import java.util.Optional
@@ -45,6 +45,7 @@
@Mock lateinit var commandQueue: CommandQueue
@Mock lateinit var bubbles: Bubbles
@Mock lateinit var optionalBubbles: Optional<Bubbles>
+ @Mock lateinit var noteTaskController: NoteTaskController
@Before
fun setUp() {
@@ -57,12 +58,13 @@
private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
return NoteTaskInitializer(
optionalBubbles = optionalBubbles,
- lazyNoteTaskController = mock(),
+ noteTaskController = noteTaskController,
commandQueue = commandQueue,
isEnabled = isEnabled,
)
}
+ // region initializer
@Test
fun initialize_shouldAddCallbacks() {
createNoteTaskInitializer().initialize()
@@ -85,4 +87,35 @@
verify(commandQueue, never()).addCallback(any())
}
+
+ @Test
+ fun initialize_flagEnabled_shouldEnableShortcut() {
+ createNoteTaskInitializer().initialize()
+
+ verify(noteTaskController).setNoteTaskShortcutEnabled(true)
+ }
+
+ @Test
+ fun initialize_flagDisabled_shouldDisableShortcut() {
+ createNoteTaskInitializer(isEnabled = false).initialize()
+
+ verify(noteTaskController).setNoteTaskShortcutEnabled(false)
+ }
+ // endregion
+
+ // region handleSystemKey
+ @Test
+ fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+ verify(noteTaskController).showNoteTask()
+ }
+
+ @Test
+ fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+ createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+ verify(noteTaskController, never()).showNoteTask()
+ }
+ // endregion
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 5e9c1aa..906c20b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -703,28 +703,32 @@
public void testParentalControls() {
// Make sure the security footer is visible, so that the images are updated.
when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
-
when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
+ // We use the default icon when there is no admin icon.
+ when(mSecurityController.getIcon(any())).thenReturn(null);
+ mFooter.refreshState();
+ TestableLooper.get(this).processAllMessages();
+ assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
+ mFooterText.getText());
+ assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+
Drawable testDrawable = new VectorDrawable();
when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
assertNotNull(mSecurityController.getIcon(null));
mFooter.refreshState();
-
TestableLooper.get(this).processAllMessages();
assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
mFooterText.getText());
assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-
assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
// Ensure the primary icon is back to default after parental controls are gone
when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
mFooter.refreshState();
TestableLooper.get(this).processAllMessages();
-
assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
}
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 9c36be6..88651c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -23,9 +23,11 @@
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,6 +39,9 @@
private lateinit var qsConstraint: ConstraintSet
private lateinit var largeScreenConstraint: ConstraintSet
+ @get:Rule
+ val expect: Expect = Expect.create()
+
@Before
fun setUp() {
qqsConstraint = ConstraintSet().apply {
@@ -344,6 +349,32 @@
}
@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) ->
+ expect.withMessage("$name changes height")
+ .that(qqsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mHeight.fromConstraint())
+ expect.withMessage("$name changes width")
+ .that(qqsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+ .isEqualTo(qsConstraint.getConstraint(id).layout.mWidth.fromConstraint())
+ }
+ }
+
+ private fun Int.fromConstraint() = when (this) {
+ -1 -> "MATCH_PARENT"
+ -2 -> "WRAP_CONTENT"
+ else -> toString()
+ }
+
+ @Test
fun testEmptyCutoutDateIconsAreConstrainedWidth() {
CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()()
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 858d0e7..1d30ad9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -46,7 +46,6 @@
import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
-import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
@@ -77,6 +76,7 @@
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -212,20 +212,6 @@
}
@Test
- fun testCorrectConstraints() {
- val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
-
- verify(qqsConstraints).load(eq(context), capture(captor))
- 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)
-
- verify(largeScreenConstraints).load(eq(context), capture(captor))
- assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
- }
-
- @Test
fun testControllersCreatedAndInitialized() {
verify(variableDateViewController).init()
@@ -287,16 +273,6 @@
}
@Test
- fun testLargeScreenActive_true() {
- controller.largeScreenActive = false // Make sure there's a change
- clearInvocations(view)
-
- controller.largeScreenActive = true
-
- verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
- }
-
- @Test
fun testLargeScreenActive_false() {
controller.largeScreenActive = true // Make sure there's a change
clearInvocations(view)
@@ -696,6 +672,25 @@
verify(clock).pivotY = height.toFloat() / 2
}
+ @Test
+ fun onDensityOrFontScaleChanged_reloadConstraints() {
+ // After density or font scale change, constraints need to be reloaded to reflect new
+ // dimensions.
+ reset(qqsConstraints)
+ reset(qsConstraints)
+ reset(largeScreenConstraints)
+
+ configurationController.notifyDensityOrFontScaleChanged()
+
+ val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+ verify(qqsConstraints).load(eq(context), capture(captor))
+ 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)
+ verify(largeScreenConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+ }
+
private fun View.executeLayoutChange(
left: Int,
top: Int,
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 17ba30a..56a840c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -43,6 +43,7 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -530,6 +531,8 @@
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
verify(mNotificationStackScrollLayoutController)
.setOnEmptySpaceClickListener(mEmptySpaceClickListenerCaptor.capture());
+ verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
+ reset(mKeyguardStatusViewController);
}
@After
@@ -609,7 +612,7 @@
@Test
public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 20,
/* indicationPadding= */ 0,
/* ambientPadding= */ 0);
@@ -620,7 +623,7 @@
@Test
public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 30,
/* ambientPadding= */ 0);
@@ -631,7 +634,7 @@
@Test
public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() {
- setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ setBottomPadding(/* stackScrollLayoutBottom= */ 180,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 0,
/* ambientPadding= */ 40);
@@ -954,7 +957,7 @@
}
@Test
- public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView() {
+ public void testFinishInflate_userSwitcherDisabled_doNotInflateUserSwitchView_initClock() {
givenViewAttached();
when(mResources.getBoolean(
com.android.internal.R.bool.config_keyguardUserSwitcher)).thenReturn(true);
@@ -965,6 +968,7 @@
mNotificationPanelViewController.onFinishInflate();
verify(mUserSwitcherStubView, never()).inflate();
+ verify(mKeyguardStatusViewController, times(3)).displayClock(LARGE, /* animate */ true);
}
@Test
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 faf4592..5431eba 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
@@ -72,6 +72,7 @@
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.StatusBarPipelineFlags;
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -245,6 +246,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mMockProvisionController,
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 ca75a40..9441d49 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
@@ -49,6 +49,7 @@
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -150,6 +151,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
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 84c242c..4c1f0a8 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
@@ -44,6 +44,7 @@
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -78,6 +79,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mMockProvisionController,
@@ -115,6 +117,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mMockProvisionController,
@@ -150,6 +153,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
@@ -188,6 +192,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
@@ -274,6 +279,7 @@
mFakeExecutor,
mCallbackHandler,
mock(AccessPointControllerImpl.class),
+ mock(StatusBarPipelineFlags.class),
mock(DataUsageController.class),
mMockSubDefaults,
mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
new file mode 100644
index 0000000..89faa239
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -0,0 +1,164 @@
+package com.android.systemui.statusbar.notification
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RoundableTest : SysuiTestCase() {
+ val targetView: View = mock()
+ val roundable = FakeRoundable(targetView)
+
+ @Test
+ fun defaultConfig_shouldNotHaveRoundedCorner() {
+ // the expected default value for the roundness is top = 0f, bottom = 0f
+ assertEquals(0f, roundable.roundableState.topRoundness)
+ assertEquals(0f, roundable.roundableState.bottomRoundness)
+ assertEquals(false, roundable.hasRoundedCorner())
+ }
+
+ @Test
+ fun applyRoundnessAndInvalidate_should_invalidate_targetView() {
+ roundable.applyRoundnessAndInvalidate()
+
+ verify(targetView, times(1)).invalidate()
+ }
+
+ @Test
+ fun requestTopRoundness_update_and_invalidate_targetView() {
+ roundable.requestTopRoundness(value = 1f, sourceType = SOURCE1)
+
+ assertEquals(1f, roundable.roundableState.topRoundness)
+ verify(targetView, times(1)).invalidate()
+ }
+
+ @Test
+ fun requestBottomRoundness_update_and_invalidate_targetView() {
+ roundable.requestBottomRoundness(value = 1f, sourceType = SOURCE1)
+
+ assertEquals(1f, roundable.roundableState.bottomRoundness)
+ verify(targetView, times(1)).invalidate()
+ }
+
+ @Test
+ fun requestRoundness_update_and_invalidate_targetView() {
+ roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+
+ assertEquals(1f, roundable.roundableState.topRoundness)
+ assertEquals(1f, roundable.roundableState.bottomRoundness)
+ verify(targetView, atLeastOnce()).invalidate()
+ }
+
+ @Test
+ fun requestRoundnessReset_update_and_invalidate_targetView() {
+ roundable.requestRoundness(1f, 1f, SOURCE1)
+ assertEquals(1f, roundable.roundableState.topRoundness)
+ assertEquals(1f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundnessReset(sourceType = SOURCE1)
+
+ assertEquals(0f, roundable.roundableState.topRoundness)
+ assertEquals(0f, roundable.roundableState.bottomRoundness)
+ verify(targetView, atLeastOnce()).invalidate()
+ }
+
+ @Test
+ fun hasRoundedCorner_return_true_ifRoundnessIsGreaterThenZero() {
+ roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+ assertEquals(true, roundable.hasRoundedCorner())
+
+ roundable.requestRoundness(top = 1f, bottom = 0f, sourceType = SOURCE1)
+ assertEquals(true, roundable.hasRoundedCorner())
+
+ roundable.requestRoundness(top = 0f, bottom = 1f, sourceType = SOURCE1)
+ assertEquals(true, roundable.hasRoundedCorner())
+
+ roundable.requestRoundness(top = 0f, bottom = 0f, sourceType = SOURCE1)
+ assertEquals(false, roundable.hasRoundedCorner())
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_lower() {
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+ assertEquals(0.1f, roundable.roundableState.topRoundness)
+ assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+ // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+ assertEquals(0.2f, roundable.roundableState.topRoundness)
+ assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_higher() {
+ roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE2)
+ // SOURCE1 has 0.5f - SOURCE2 has 0.1f
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_higher_second_step() {
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+ assertEquals(0.1f, roundable.roundableState.topRoundness)
+ assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+ // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+ assertEquals(0.2f, roundable.roundableState.topRoundness)
+ assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.3f, 0.3f, SOURCE1)
+ // SOURCE1 has 0.3f - SOURCE2 has 0.2f
+ assertEquals(0.3f, roundable.roundableState.topRoundness)
+ assertEquals(0.3f, roundable.roundableState.bottomRoundness)
+ }
+
+ @Test
+ fun roundness_take_maxValue_onMultipleSources_first_lower_second_step() {
+ roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+ // SOURCE1 has 0.5f - SOURCE2 has 0.2f
+ assertEquals(0.5f, roundable.roundableState.topRoundness)
+ assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+ roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+ // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+ assertEquals(0.2f, roundable.roundableState.topRoundness)
+ assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+ }
+
+ class FakeRoundable(
+ targetView: View,
+ radius: Float = MAX_RADIUS,
+ ) : Roundable {
+ override val roundableState =
+ RoundableState(
+ targetView = targetView,
+ roundable = this,
+ maxRadius = radius,
+ )
+ }
+
+ companion object {
+ private const val MAX_RADIUS = 10f
+ private val SOURCE1 = SourceType.from("Source1")
+ private val SOURCE2 = SourceType.from("Source2")
+ }
+}
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 088d165..59d4720 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
@@ -213,8 +213,7 @@
SourceType sourceType
) throws Exception {
ExpandableNotificationRow row = createRow();
- row.requestTopRoundness(topRoundness, false, sourceType);
- row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+ row.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
return row;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 438b528..fd1944e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,7 +25,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
@@ -158,7 +158,7 @@
ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnScroll);
+ /* sourceType = */ LegacySourceType.OnScroll);
mChildrenContainer.addNotification(row, 0);
@@ -171,11 +171,11 @@
ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.DefaultValue);
+ /* sourceType = */ LegacySourceType.DefaultValue);
ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnDismissAnimation);
+ /* sourceType = */ LegacySourceType.OnDismissAnimation);
mChildrenContainer.addNotification(row1, 0);
mChildrenContainer.addNotification(row2, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 8c8b644..bd0a556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
@@ -73,7 +74,8 @@
mRoundnessManager = new NotificationRoundnessManager(
new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
mLogger,
- mock(DumpManager.class));
+ mock(DumpManager.class),
+ mock(FeatureFlags.class));
allowTestableLooperAsMainThread();
NotificationTestHelper testHelper = new NotificationTestHelper(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ecc0224..30da08e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -59,10 +60,12 @@
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
@Mock private MediaContainerController mMediaContainerController;
+ @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private SectionHeaderController mIncomingHeaderController;
@Mock private SectionHeaderController mPeopleHeaderController;
@Mock private SectionHeaderController mAlertingHeaderController;
@Mock private SectionHeaderController mSilentHeaderController;
+ @Mock private FeatureFlags mFeatureFlag;
private NotificationSectionsManager mSectionsManager;
@@ -89,10 +92,12 @@
mKeyguardMediaController,
mSectionsFeatureManager,
mMediaContainerController,
+ mNotificationRoundnessManager,
mIncomingHeaderController,
mPeopleHeaderController,
mAlertingHeaderController,
- mSilentHeaderController
+ mSilentHeaderController,
+ mFeatureFlag
);
// Required in order for the header inflation to work properly
when(mNssl.generateLayoutParams(any(AttributeSet.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index bda2336..9d759c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -9,7 +9,7 @@
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.SourceType
+import com.android.systemui.statusbar.notification.LegacySourceType
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
@@ -314,9 +314,9 @@
val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnScroll)
+ /* sourceType = */ LegacySourceType.OnScroll)
- NotificationShelf.resetOnScrollRoundness(row)
+ NotificationShelf.resetLegacyOnScrollRoundness(row)
assertEquals(0f, row.topRoundness)
assertEquals(0f, row.bottomRoundness)
@@ -327,14 +327,14 @@
val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.DefaultValue)
+ /* sourceType = */ LegacySourceType.DefaultValue)
val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
/* topRoundness = */ 1f,
/* bottomRoundness = */ 1f,
- /* sourceType = */ SourceType.OnDismissAnimation)
+ /* sourceType = */ LegacySourceType.OnDismissAnimation)
- NotificationShelf.resetOnScrollRoundness(row1)
- NotificationShelf.resetOnScrollRoundness(row2)
+ NotificationShelf.resetLegacyOnScrollRoundness(row1)
+ NotificationShelf.resetLegacyOnScrollRoundness(row2)
assertEquals(1f, row1.topRoundness)
assertEquals(1f, row1.bottomRoundness)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 4ea1c71..680a323 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -74,6 +74,7 @@
private NotificationSwipeHelper mSwipeHelper;
private NotificationSwipeHelper.NotificationCallback mCallback;
private NotificationMenuRowPlugin.OnMenuEventListener mListener;
+ private NotificationRoundnessManager mNotificationRoundnessManager;
private View mView;
private MotionEvent mEvent;
private NotificationMenuRowPlugin mMenuRow;
@@ -92,10 +93,17 @@
public void setUp() throws Exception {
mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
+ mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
mFeatureFlags = mock(FeatureFlags.class);
mSwipeHelper = spy(new NotificationSwipeHelper(
- mContext.getResources(), ViewConfiguration.get(mContext),
- new FalsingManagerFake(), mFeatureFlags, SwipeHelper.X, mCallback, mListener));
+ mContext.getResources(),
+ ViewConfiguration.get(mContext),
+ new FalsingManagerFake(),
+ mFeatureFlags,
+ SwipeHelper.X,
+ mCallback,
+ mListener,
+ mNotificationRoundnessManager));
mView = mock(View.class);
mEvent = mock(MotionEvent.class);
mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index a2e9230..81a3f12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -35,7 +35,7 @@
) =
NotificationTargetsHelper(
FakeFeatureFlags().apply {
- set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+ set(Flags.USE_ROUNDNESS_SOURCETYPES, notificationGroupCorner)
}
)
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 038af8f..6155e3c 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
@@ -27,6 +27,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
@@ -295,6 +296,7 @@
}
@Test
+ @Ignore("b/261408895")
fun equivalentConfigObject_listenerNotNotified() {
val config = mContext.resources.configuration
val listener = createAndAddListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 103b7b42..9727b6c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -32,6 +32,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -40,6 +41,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +73,8 @@
private NotificationWakeUpCoordinator mWakeUpCoordinator;
private KeyguardStateController mKeyguardStateController;
private CommandQueue mCommandQueue;
+ private NotificationRoundnessManager mNotificationRoundnessManager;
+ private FeatureFlags mFeatureFlag;
@Before
public void setUp() throws Exception {
@@ -89,6 +93,8 @@
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
mKeyguardStateController = mock(KeyguardStateController.class);
mCommandQueue = mock(CommandQueue.class);
+ mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
+ mFeatureFlag = mock(FeatureFlags.class);
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mock(NotificationIconAreaController.class),
mHeadsUpManager,
@@ -100,6 +106,8 @@
mCommandQueue,
mStackScrollerController,
mPanelView,
+ mNotificationRoundnessManager,
+ mFeatureFlag,
mHeadsUpStatusBarView,
new Clock(mContext, null),
Optional.of(mOperatorNameView));
@@ -182,6 +190,8 @@
mCommandQueue,
mStackScrollerController,
mPanelView,
+ mNotificationRoundnessManager,
+ mFeatureFlag,
mHeadsUpStatusBarView,
new Clock(mContext, null),
Optional.empty());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 5ce51bb..aa7ab0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -31,6 +31,7 @@
import android.telephony.TelephonyManager.DATA_CONNECTING
import android.telephony.TelephonyManager.DATA_DISCONNECTED
import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
@@ -221,6 +222,21 @@
}
@Test
+ fun testFlowForSubId_dataConnectionState_unknown() =
+ runBlocking(IMMEDIATE) {
+ var latest: MobileSubscriptionModel? = null
+ val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+ val callback =
+ getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+ callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+ assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+
+ job.cancel()
+ }
+
+ @Test
fun testFlowForSubId_dataActivity() =
runBlocking(IMMEDIATE) {
var latest: MobileSubscriptionModel? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 5c16e129..3d9fd96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,6 +25,7 @@
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -64,6 +65,7 @@
private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock
private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock
private lateinit var connectivityConstants: ConnectivityConstants
@Mock
@@ -103,6 +105,7 @@
connectivityConstants,
context,
logger,
+ tableLogBuffer,
interactor,
scope,
statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 3001b81..12b93819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,6 +23,7 @@
import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -40,6 +41,7 @@
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
@@ -67,6 +69,7 @@
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -123,6 +126,7 @@
connectivityConstants,
context,
logger,
+ tableLogBuffer,
interactor,
scope,
statusBarPipelineFlags,
@@ -137,15 +141,21 @@
yield()
// THEN we get the expected icon
- assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
- val expectedContentDescription =
- if (testCase.expected == null) {
- null
- } else {
- testCase.expected.contentDescription.invoke(context)
+ val actualIcon = iconFlow.value
+ when (testCase.expected) {
+ null -> {
+ assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java)
}
- assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
- .isEqualTo(expectedContentDescription)
+ else -> {
+ assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java)
+ val actualIconVisible = actualIcon as WifiIcon.Visible
+ assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource)
+ val expectedContentDescription =
+ testCase.expected.contentDescription.invoke(context)
+ assertThat(actualIconVisible.contentDescription.loadContentDescription(context))
+ .isEqualTo(expectedContentDescription)
+ }
+ }
job.cancel()
}
@@ -174,7 +184,7 @@
val isDefault: Boolean = false,
val network: WifiNetworkModel,
- /** The expected output. Null if we expect the output to be null. */
+ /** The expected output. Null if we expect the output to be hidden. */
val expected: Expected?
) {
override fun toString(): String {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 6a6b2a8..7502020 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,7 +18,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -34,6 +34,7 @@
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -59,6 +60,7 @@
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
+ @Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -103,21 +105,21 @@
@Test
fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
- var latestHome: Icon? = null
+ var latestHome: WifiIcon? = null
val jobHome = underTest
.home
.wifiIcon
.onEach { latestHome = it }
.launchIn(this)
- var latestKeyguard: Icon? = null
+ var latestKeyguard: WifiIcon? = null
val jobKeyguard = underTest
.keyguard
.wifiIcon
.onEach { latestKeyguard = it }
.launchIn(this)
- var latestQs: Icon? = null
+ var latestQs: WifiIcon? = null
val jobQs = underTest
.qs
.wifiIcon
@@ -133,7 +135,7 @@
)
yield()
- assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+ assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
assertThat(latestHome).isEqualTo(latestKeyguard)
assertThat(latestKeyguard).isEqualTo(latestQs)
@@ -541,6 +543,7 @@
connectivityConstants,
context,
logger,
+ tableLogBuffer,
interactor,
scope,
statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3769f52..915ea1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -170,6 +170,34 @@
}
@Test
+ public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, times(1)).onStateChanged(any());
+ }
+
+ @Test
+ public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, never()).onStateChanged(any());
+ }
+
+ @Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
new file mode 100644
index 0000000..3767fbe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.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 com.android.systemui;
+
+import android.os.Debug;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Convenience class for grabbing a heap dump after a test class is run.
+ *
+ * To use:
+ * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
+ * - adb pull /path/to/something.ahprof
+ * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
+ * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof
+ * - Watch output for next steps
+ * - Profit and fix leaks!
+ */
+public class MemoryTrackingTestCase extends SysuiTestCase {
+ private static File sFilesDir = null;
+ private static String sLatestTestClassName = null;
+
+ @Before public void grabFilesDir() {
+ if (sFilesDir == null) {
+ sFilesDir = mContext.getFilesDir();
+ }
+ sLatestTestClassName = getClass().getName();
+ }
+
+ @AfterClass
+ public static void dumpHeap() throws IOException {
+ if (sFilesDir == null) {
+ Log.e("MEMORY", "Somehow no test cases??");
+ return;
+ }
+ mockitoTearDown();
+ Log.w("MEMORY", "about to dump heap");
+ File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+ Debug.dumpHprofData(path.getPath());
+ Log.w("MEMORY", "did it! Location: " + path);
+ }
+}
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 21e16a1..8a10bf06 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
@@ -81,11 +81,6 @@
properties.get(namespace).put(name, value);
}
- @Override
- public void enforceReadPermission(String namespace) {
- // no-op
- }
-
private Properties propsForNamespaceAndName(String namespace, String name) {
if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
return new Properties.Builder(namespace)
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index abd08de..ccff624 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -2,3 +2,4 @@
per-file wifi.proto = file:/wifi/OWNERS
per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
per-file system_messages.proto = file:/core/res/OWNERS
+per-file altitude.proto = file:/location/OWNERS
diff --git a/proto/src/altitude.proto b/proto/src/altitude.proto
new file mode 100644
index 0000000..1010f67
--- /dev/null
+++ b/proto/src/altitude.proto
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.location.altitude;
+
+option java_package = "com.android.internal.location.altitude";
+option java_multiple_files = true;
+
+// Defines parameters for a spherically projected geoid map and corresponding
+// tile management.
+message MapParamsProto {
+ // Defines the resolution of the map in terms of an S2 level.
+ optional int32 map_s2_level = 1;
+ // Defines the resolution of the tiles in cache in terms of an S2 level.
+ optional int32 cache_tile_s2_level = 2;
+ // Defines the resolution of the tiles on disk in terms of an S2 level.
+ optional int32 disk_tile_s2_level = 3;
+ // Defines the `a` coefficient in the expression `a * map_value + b` used to
+ // calculate a geoid height in meters.
+ optional double model_a_meters = 4;
+ // Defines the `b` coefficient in the expression `a * map_value + b` used to
+ // calculate a geoid height in meters.
+ optional double model_b_meters = 5;
+ // Defines the root mean square error in meters of the geoid height.
+ optional double model_rmse_meters = 6;
+}
+
+// A single tile associating values in the unit interval [0, 1] to map cells.
+message S2TileProto {
+ // The S2 token associated with the common parent of all map cells in this
+ // tile.
+ optional string tile_key = 1;
+
+ // Encoded data that merge into a value in the unit interval [0, 1] for each
+ // map cell in this tile.
+ optional bytes byte_buffer = 2;
+ optional bytes byte_jpeg = 3;
+ optional bytes byte_png = 4;
+}
diff --git a/services/api/current.txt b/services/api/current.txt
index 834ed2f..18b1df3 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -59,6 +59,7 @@
method public void close();
method public void forAllPackageStates(@NonNull java.util.function.Consumer<com.android.server.pm.pkg.PackageState>);
method @Nullable public com.android.server.pm.pkg.PackageState getPackageState(@NonNull String);
+ method @NonNull public java.util.Map<java.lang.String,com.android.server.pm.pkg.PackageState> getPackageStates();
}
public static interface PackageManagerLocal.UnfilteredSnapshot extends java.lang.AutoCloseable {
@@ -98,6 +99,7 @@
public interface PackageState {
method @Nullable public com.android.server.pm.pkg.AndroidPackage getAndroidPackage();
method public int getAppId();
+ method public int getHiddenApiEnforcementPolicy();
method @NonNull public String getPackageName();
method @Nullable public String getPrimaryCpuAbi();
method @Nullable public String getSeInfo();
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 f1ba5ff..5e68d52 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,8 +16,7 @@
package com.android.server.companion.virtual;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.virtual.VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS;
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -26,10 +25,10 @@
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
-import android.companion.AssociationRequest;
import android.companion.virtual.VirtualDeviceManager.ActivityListener;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.VirtualDeviceParams.ActivityPolicy;
+import android.companion.virtual.VirtualDeviceParams.RecentsPolicy;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
@@ -127,10 +126,10 @@
@GuardedBy("mGenericWindowPolicyControllerLock")
private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
new ArraySet<>();
- @Nullable
- private final @AssociationRequest.DeviceProfile String mDeviceProfile;
@Nullable private final SecureWindowCallback mSecureWindowCallback;
@Nullable private final List<String> mDisplayCategories;
+ @RecentsPolicy
+ private final int mDefaultRecentsPolicy;
/**
* Creates a window policy controller that is generic to the different use cases of virtual
@@ -156,7 +155,7 @@
* launching.
* @param secureWindowCallback Callback that is called when a secure window shows on the
* virtual display.
- * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
+ * @param defaultRecentsPolicy a policy to indicate how to handle activities in recents.
*/
public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
@NonNull ArraySet<UserHandle> allowedUsers,
@@ -169,8 +168,8 @@
@NonNull PipBlockedCallback pipBlockedCallback,
@NonNull ActivityBlockedCallback activityBlockedCallback,
@NonNull SecureWindowCallback secureWindowCallback,
- @AssociationRequest.DeviceProfile String deviceProfile,
- @NonNull List<String> displayCategories) {
+ @NonNull List<String> displayCategories,
+ @RecentsPolicy int defaultRecentsPolicy) {
super();
mAllowedUsers = allowedUsers;
mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
@@ -181,10 +180,10 @@
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
- mDeviceProfile = deviceProfile;
mPipBlockedCallback = pipBlockedCallback;
mSecureWindowCallback = secureWindowCallback;
mDisplayCategories = displayCategories;
+ mDefaultRecentsPolicy = defaultRecentsPolicy;
}
/**
@@ -318,18 +317,8 @@
}
@Override
- public boolean canShowTasksInRecents() {
- if (mDeviceProfile == null) {
- return true;
- }
- // TODO(b/234075973) : Remove this once proper API is ready.
- switch (mDeviceProfile) {
- case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
- return false;
- case DEVICE_PROFILE_APP_STREAMING:
- default:
- return true;
- }
+ public boolean canShowTasksInHostDeviceRecents() {
+ return (mDefaultRecentsPolicy & RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS) != 0;
}
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 02053cc..96c71e5 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.StringDef;
-import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
@@ -178,13 +177,14 @@
int productId,
@NonNull IBinder deviceToken,
int displayId,
- @NonNull Point screenSize) {
+ int height,
+ int width) {
final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
try {
createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
productId, deviceToken, displayId, phys,
() -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
- phys, screenSize.y, screenSize.x));
+ phys, height, width));
} catch (DeviceCreationException e) {
throw new RuntimeException(
"Failed to create virtual touchscreen device '" + deviceName + "'.", e);
@@ -414,17 +414,25 @@
}
@VisibleForTesting
- void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId,
- String phys, int inputDeviceId) {
+ void addDeviceForTesting(IBinder deviceToken, int fd, int type, int displayId, String phys,
+ int inputDeviceId) {
synchronized (mLock) {
- mInputDeviceDescriptors.put(deviceToken,
- new InputDeviceDescriptor(fd, () -> {}, type, displayId, phys,
- inputDeviceId));
+ mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(fd, () -> {
+ }, type, displayId, phys, inputDeviceId));
}
}
- private static native int nativeOpenUinputDpad(String deviceName, int vendorId,
- int productId, String phys);
+ @VisibleForTesting
+ Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() {
+ final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>();
+ synchronized (mLock) {
+ inputDeviceDescriptors.putAll(mInputDeviceDescriptors);
+ }
+ return inputDeviceDescriptors;
+ }
+
+ private static native int nativeOpenUinputDpad(String deviceName, int vendorId, int productId,
+ String phys);
private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
int productId, String phys);
private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
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 828f302..5819861 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -44,14 +44,17 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.display.DisplayManager;
+import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.os.Binder;
import android.os.IBinder;
import android.os.Looper;
@@ -397,19 +400,12 @@
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualDpad(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual dpad");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual dpad for a display not associated with "
+ "this virtual device");
@@ -417,26 +413,19 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
- displayId);
+ mInputController.createDpad(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualKeyboard(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual keyboard");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual keyboard for a display not associated with "
+ "this virtual device");
@@ -444,26 +433,19 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
- displayId);
+ mInputController.createKeyboard(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualMouse(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual mouse");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual mouse for a display not associated with this "
+ "virtual device");
@@ -471,42 +453,38 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
+ mInputController.createMouse(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@Override // Binder call
- public void createVirtualTouchscreen(
- int displayId,
- @NonNull String deviceName,
- int vendorId,
- int productId,
- @NonNull IBinder deviceToken,
- @NonNull Point screenSize) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
+ @NonNull IBinder deviceToken) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to create a virtual touchscreen");
synchronized (mVirtualDeviceLock) {
- if (!mVirtualDisplayIds.contains(displayId)) {
+ if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
throw new SecurityException(
"Cannot create a virtual touchscreen for a display not associated with "
+ "this virtual device");
}
}
-
- if (screenSize.x <= 0 || screenSize.y <= 0) {
+ int screenHeightPixels = config.getHeightInPixels();
+ int screenWidthPixels = config.getWidthInPixels();
+ if (screenHeightPixels <= 0 || screenWidthPixels <= 0) {
throw new IllegalArgumentException(
"Cannot create a virtual touchscreen, screen dimensions must be positive. Got: "
- + screenSize);
+ + "(" + screenWidthPixels + ", " + screenHeightPixels + ")");
}
final long ident = Binder.clearCallingIdentity();
try {
- mInputController.createTouchscreen(deviceName, vendorId, productId,
- deviceToken, displayId, screenSize);
+ mInputController.createTouchscreen(config.getInputDeviceName(), config.getVendorId(),
+ config.getProductId(), deviceToken, config.getAssociatedDisplayId(),
+ screenHeightPixels, screenWidthPixels);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -701,8 +679,8 @@
this::onEnteringPipBlocked,
this::onActivityBlocked,
this::onSecureWindowShown,
- mAssociationInfo.getDeviceProfile(),
- displayCategories);
+ displayCategories,
+ mParams.getDefaultRecentsPolicy());
gwpc.registerRunningAppsChangedListener(/* listener= */ this);
return gwpc;
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bd90d85..32afcca 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1041,7 +1041,7 @@
String callingFeatureId, IPhoneStateListener callback,
int[] events, boolean notifyNow) {
Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
- listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+ listen(renounceFineLocationAccess, renounceCoarseLocationAccess, callingPackage,
callingFeatureId, callback, eventList, notifyNow, subId);
}
@@ -1606,7 +1606,7 @@
if (DBG) {
log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
+ " subId=" + subId + " phoneId=" + phoneId
- + " state=" + state);
+ + " state=" + stateToSend);
}
r.callback.onServiceStateChanged(stateToSend);
} catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 2a44e30..0d672bd 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -3016,7 +3016,6 @@
final TimeoutRecord tr = TimeoutRecord.forShortFgsTimeout(reason);
- // TODO(short-service): TODO Add SHORT_FGS_TIMEOUT to AnrLatencyTracker
tr.mLatencyTracker.waitingOnAMSLockStarted();
synchronized (mAm) {
tr.mLatencyTracker.waitingOnAMSLockEnded();
@@ -3838,6 +3837,11 @@
throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ className + " is not an isolatedProcess");
}
+ if (AppGlobals.getPackageManager().getPackageUid(callingPackage,
+ 0, userId) != callingUid) {
+ throw new SecurityException("BIND_EXTERNAL_SERVICE failed, "
+ + "calling package not owned by calling UID ");
+ }
// Run the service under the calling package's application.
ApplicationInfo aInfo = AppGlobals.getPackageManager().getApplicationInfo(
callingPackage, ActivityManagerService.STOCK_PM_FLAGS, userId);
@@ -5421,6 +5425,13 @@
// This is a call from a service start... take care of
// book-keeping.
r.callStart = true;
+
+ // Set the result to startCommandResult.
+ // START_TASK_REMOVED_COMPLETE is _not_ a result from onStartCommand(), so
+ // let's ignore.
+ if (res != Service.START_TASK_REMOVED_COMPLETE) {
+ r.startCommandResult = res;
+ }
switch (res) {
case Service.START_STICKY_COMPATIBILITY:
case Service.START_STICKY: {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7566bab..efe14f4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18324,7 +18324,7 @@
public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
- BroadcastLoopers.waitForIdle(pw);
+ BroadcastLoopers.waitForBarrier(pw);
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForBarrier(pw);
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 1eebd01..f5d1c10 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,12 +153,27 @@
"bcast_extra_running_urgent_process_queues";
private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
+ /**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent
+ * broadcast dispatches allowed before letting broadcasts in lower priority queue
+ * to be scheduled in order to avoid starvation.
+ */
public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
"bcast_max_consecutive_urgent_dispatches";
private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
/**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal
+ * broadcast dispatches allowed before letting broadcasts in lower priority queue
+ * to be scheduled in order to avoid starvation.
+ */
+ public int MAX_CONSECUTIVE_NORMAL_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES;
+ private static final String KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES =
+ "bcast_max_consecutive_normal_dispatches";
+ private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10;
+
+ /**
* For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
* to dispatch to a "running" process queue before we retire them back to
* being "runnable" to give other processes a chance to run.
@@ -341,6 +356,9 @@
MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
+ MAX_CONSECUTIVE_NORMAL_DISPATCHES = getDeviceConfigInt(
+ KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+ DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES);
MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
@@ -396,6 +414,10 @@
TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+ pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+ MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
+ pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+ MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
pw.decreaseIndent();
pw.println();
}
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index b828720..a5535cb 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.MessageQueue;
@@ -30,6 +31,7 @@
import java.io.PrintWriter;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
/**
* Collection of {@link Looper} that are known to be used for broadcast dispatch
@@ -73,19 +75,44 @@
* still in the future are ignored for the purposes of the idle test.
*/
public static void waitForIdle(@Nullable PrintWriter pw) {
+ waitForCondition(pw, (looper, latch) -> {
+ final MessageQueue queue = looper.getQueue();
+ queue.addIdleHandler(() -> {
+ latch.countDown();
+ return false;
+ });
+ });
+ }
+
+ /**
+ * Wait for all registered {@link Looper} instances to handle currently waiting messages.
+ * Note that {@link Message#when} still in the future are ignored for the purposes
+ * of the idle test.
+ */
+ public static void waitForBarrier(@Nullable PrintWriter pw) {
+ waitForCondition(pw, (looper, latch) -> {
+ (new Handler(looper)).post(() -> {
+ latch.countDown();
+ });
+ });
+ }
+
+ /**
+ * Wait for all registered {@link Looper} instances to meet a certain condition.
+ */
+ private static void waitForCondition(@Nullable PrintWriter pw,
+ @NonNull BiConsumer<Looper, CountDownLatch> condition) {
final CountDownLatch latch;
synchronized (sLoopers) {
final int N = sLoopers.size();
latch = new CountDownLatch(N);
for (int i = 0; i < N; i++) {
- final MessageQueue queue = sLoopers.valueAt(i).getQueue();
+ final Looper looper = sLoopers.valueAt(i);
+ final MessageQueue queue = looper.getQueue();
if (queue.isIdle()) {
latch.countDown();
} else {
- queue.addIdleHandler(() -> {
- latch.countDown();
- return false;
- });
+ condition.accept(looper, latch);
}
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 66d7fc9..672392d 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -153,6 +153,12 @@
private int mActiveCountConsecutiveUrgent;
/**
+ * Number of consecutive normal broadcasts that have been dispatched
+ * since the last offload dispatch.
+ */
+ private int mActiveCountConsecutiveNormal;
+
+ /**
* Count of pending broadcasts of these various flavors.
*/
private int mCountForeground;
@@ -551,48 +557,63 @@
* Will thrown an exception if there are no pending broadcasts; relies on
* {@link #isEmpty()} being false.
*/
- SomeArgs removeNextBroadcast() {
+ private @Nullable SomeArgs removeNextBroadcast() {
final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
if (queue == mPendingUrgent) {
mActiveCountConsecutiveUrgent++;
- } else {
+ } else if (queue == mPending) {
mActiveCountConsecutiveUrgent = 0;
+ mActiveCountConsecutiveNormal++;
+ } else if (queue == mPendingOffload) {
+ mActiveCountConsecutiveUrgent = 0;
+ mActiveCountConsecutiveNormal = 0;
}
- return queue.removeFirst();
+ return !isQueueEmpty(queue) ? queue.removeFirst() : null;
}
@Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
- ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
- ArrayDeque<SomeArgs> nextNormal = null;
- if (!mPending.isEmpty()) {
- nextNormal = mPending;
- } else if (!mPendingOffload.isEmpty()) {
- nextNormal = mPendingOffload;
+ final ArrayDeque<SomeArgs> nextNormal = queueForNextBroadcast(
+ mPending, mPendingOffload,
+ mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES);
+ final ArrayDeque<SomeArgs> nextBroadcastQueue = queueForNextBroadcast(
+ mPendingUrgent, nextNormal,
+ mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES);
+ return nextBroadcastQueue;
+ }
+
+ private @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast(
+ @Nullable ArrayDeque<SomeArgs> highPriorityQueue,
+ @Nullable ArrayDeque<SomeArgs> lowPriorityQueue,
+ int consecutiveHighPriorityCount,
+ int maxHighPriorityDispatchLimit) {
+ // nothing high priority pending, no further decisionmaking
+ if (isQueueEmpty(highPriorityQueue)) {
+ return lowPriorityQueue;
}
- // nothing urgent pending, no further decisionmaking
- if (nextUrgent == null) {
- return nextNormal;
- }
- // nothing but urgent pending, also no further decisionmaking
- if (nextNormal == null) {
- return nextUrgent;
+ // nothing but high priority pending, also no further decisionmaking
+ if (isQueueEmpty(lowPriorityQueue)) {
+ return highPriorityQueue;
}
- // Starvation mitigation: although we prioritize urgent broadcasts by default,
- // we allow non-urgent deliveries to make steady progress even if urgent
- // broadcasts are arriving faster than they can be dispatched.
+ // Starvation mitigation: although we prioritize high priority queues by default,
+ // we allow low priority queues to make steady progress even if broadcasts in
+ // high priority queue are arriving faster than they can be dispatched.
//
- // We do not try to defer to the next non-urgent broadcast if that broadcast
+ // We do not try to defer to the next broadcast in low priority queues if that broadcast
// is ordered and still blocked on delivery to other recipients.
- final SomeArgs nextNormalArgs = nextNormal.peekFirst();
- final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
- final int nextNormalIndex = nextNormalArgs.argi1;
- final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
- final boolean canTakeNormal =
- mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
- && rNormal.enqueueTime <= rUrgent.enqueueTime
- && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
- return canTakeNormal ? nextNormal : nextUrgent;
+ final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst();
+ final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
+ final int nextLPRecordIndex = nextLPArgs.argi1;
+ final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
+ final boolean isLPQueueEligible =
+ consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit
+ && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
+ && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+ return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
+ }
+
+ private static boolean isQueueEmpty(@Nullable ArrayDeque<SomeArgs> queue) {
+ return (queue == null || queue.isEmpty());
}
/**
@@ -600,13 +621,13 @@
*/
@Nullable SomeArgs peekNextBroadcast() {
ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
- return (queue != null) ? queue.peekFirst() : null;
+ return !isQueueEmpty(queue) ? queue.peekFirst() : null;
}
@VisibleForTesting
@Nullable BroadcastRecord peekNextBroadcastRecord() {
ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
- return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
+ return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5d5dbbb..e9340e9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -220,10 +220,19 @@
private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState, @NonNull String reason) {
+ enqueueFinishReceiver(queue, queue.getActive(), queue.getActiveIndex(),
+ deliveryState, reason);
+ }
+
+ private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+ @NonNull BroadcastRecord r, int index,
+ @DeliveryState int deliveryState, @NonNull String reason) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = queue;
args.argi1 = deliveryState;
args.arg2 = reason;
+ args.arg3 = r;
+ args.argi2 = index;
mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
}
@@ -271,8 +280,10 @@
final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
final int deliveryState = args.argi1;
final String reason = (String) args.arg2;
+ final BroadcastRecord r = (BroadcastRecord) args.arg3;
+ final int index = args.argi2;
args.recycle();
- finishReceiverLocked(queue, deliveryState, reason);
+ finishReceiverLocked(queue, deliveryState, reason, r, index);
}
return true;
}
@@ -729,10 +740,8 @@
private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
checkState(queue.isActive(), "isActive");
- final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
- final Object receiver = r.receivers.get(index);
if (r.terminalCount == 0) {
r.dispatchTime = SystemClock.uptimeMillis();
@@ -740,26 +749,40 @@
r.dispatchClockTime = System.currentTimeMillis();
}
- // If someone already finished this broadcast, finish immediately
+ if (maybeSkipReceiver(queue, r, index)) {
+ return;
+ }
+ dispatchReceivers(queue, r, index);
+ }
+
+ /**
+ * Examine a receiver and possibly skip it. The method returns true if the receiver is
+ * skipped (and therefore no more work is required).
+ */
+ private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
final int oldDeliveryState = getDeliveryState(r, index);
+ final ProcessRecord app = queue.app;
+ final Object receiver = r.receivers.get(index);
+
+ // If someone already finished this broadcast, finish immediately
if (isDeliveryStateTerminal(oldDeliveryState)) {
enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
- return;
+ return true;
}
// Consider additional cases where we'd want to finish immediately
if (app.isInFullBackup()) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
- return;
+ return true;
}
if (mSkipPolicy.shouldSkip(r, receiver)) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
- return;
+ return true;
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
- return;
+ return true;
}
// Ignore registered receivers from a previous PID
@@ -767,12 +790,29 @@
&& ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
"BroadcastFilter for mismatched PID");
- return;
+ return true;
}
+ // The receiver was not handled in this method.
+ return false;
+ }
+
+ /**
+ * Return true if this receiver should be assumed to have been delivered.
+ */
+ private boolean isAssumedDelivered(BroadcastRecord r, int index) {
+ return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+ }
+
+ /**
+ * A receiver is about to be dispatched. Start ANR timers, if necessary.
+ */
+ private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+ final ProcessRecord app = queue.app;
+ final Object receiver = r.receivers.get(index);
// Skip ANR tracking early during boot, when requested, or when we
// immediately assume delivery success
- final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+ final boolean assumeDelivered = isAssumedDelivered(r, index);
if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
@@ -805,6 +845,7 @@
setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
"scheduleReceiverWarmLocked");
+ final Intent receiverIntent = r.getReceiverIntent(receiver);
final IApplicationThread thread = app.getOnewayThread();
if (thread != null) {
try {
@@ -920,6 +961,19 @@
return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
}
+ /**
+ * Return true if there are more broadcasts in the queue and the queue is runnable.
+ */
+ private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) {
+ // If we've made reasonable progress, periodically retire ourselves to
+ // avoid starvation of other processes and stack overflow when a
+ // broadcast is immediately finished without waiting
+ final boolean shouldRetire =
+ (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+
+ return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
+ }
+
private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
@DeliveryState int deliveryState, @NonNull String reason) {
if (!queue.isActive()) {
@@ -927,10 +981,21 @@
return false;
}
- final int cookie = traceBegin("finishReceiver");
- final ProcessRecord app = queue.app;
final BroadcastRecord r = queue.getActive();
final int index = queue.getActiveIndex();
+ return finishReceiverLocked(queue, deliveryState, reason, r, index);
+ }
+
+ private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+ @DeliveryState int deliveryState, @NonNull String reason,
+ BroadcastRecord r, int index) {
+ if (!queue.isActive()) {
+ logw("Ignoring finish; no active broadcast for " + queue);
+ return false;
+ }
+
+ final int cookie = traceBegin("finishReceiver");
+ final ProcessRecord app = queue.app;
final Object receiver = r.receivers.get(index);
setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
@@ -945,18 +1010,11 @@
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
}
- // If we've made reasonable progress, periodically retire ourselves to
- // avoid starvation of other processes and stack overflow when a
- // broadcast is immediately finished without waiting
- final boolean shouldRetire =
- (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
-
- final boolean res;
- if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
+ final boolean res = shouldContinueScheduling(queue);
+ if (res) {
// We're on a roll; move onto the next broadcast for this process
queue.makeActiveNextPending();
scheduleReceiverWarmLocked(queue);
- res = true;
} else {
// We've drained running broadcasts; maybe move back to runnable
queue.makeActiveIdle();
@@ -970,7 +1028,6 @@
// Tell other OS components that app is not actively running, giving
// a chance to update OOM adjustment
notifyStoppedRunning(queue);
- res = false;
}
traceEnd(cookie);
return res;
@@ -1442,10 +1499,10 @@
private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
mService.notifyBroadcastFinishedLocked(r);
- mHistory.addBroadcastToHistoryLocked(r);
-
r.finishTime = SystemClock.uptimeMillis();
r.nextReceiver = r.receivers.size();
+ mHistory.addBroadcastToHistoryLocked(r);
+
BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
if (r.intent.getComponent() == null && r.intent.getPackage() == null
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 0d1a6d5..ef195aa 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -128,6 +128,7 @@
boolean delayedStop; // service has been stopped but is in a delayed start?
boolean stopIfKilled; // last onStart() said to stop if service killed?
boolean callStart; // last onStart() has asked to always be called on restart.
+ int startCommandResult; // last result from onStartCommand(), only for dumpsys.
int executeNesting; // number of outstanding operations keeping foreground.
boolean executeFg; // should we be executing in the foreground?
long executingStart; // start time of last execute request.
@@ -478,6 +479,7 @@
proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+ proto.write(ServiceRecordProto.Start.START_COMMAND_RESULT, startCommandResult);
proto.end(startToken);
}
@@ -533,9 +535,22 @@
}
}
}
- proto.end(token);
+ if (mShortFgsInfo != null && mShortFgsInfo.isCurrent()) {
+ final long shortFgsToken = proto.start(ServiceRecordProto.SHORT_FGS_INFO);
+ proto.write(ServiceRecordProto.ShortFgsInfo.START_TIME,
+ mShortFgsInfo.getStartTime());
+ proto.write(ServiceRecordProto.ShortFgsInfo.START_ID,
+ mShortFgsInfo.getStartId());
+ proto.write(ServiceRecordProto.ShortFgsInfo.TIMEOUT_TIME,
+ mShortFgsInfo.getTimeoutTime());
+ proto.write(ServiceRecordProto.ShortFgsInfo.PROC_STATE_DEMOTE_TIME,
+ mShortFgsInfo.getProcStateDemoteTime());
+ proto.write(ServiceRecordProto.ShortFgsInfo.ANR_TIME,
+ mShortFgsInfo.getAnrTime());
+ proto.end(shortFgsToken);
+ }
- // TODO(short-service) Add FGS info
+ proto.end(token);
}
void dump(PrintWriter pw, String prefix) {
@@ -632,6 +647,7 @@
pw.print(" stopIfKilled="); pw.print(stopIfKilled);
pw.print(" callStart="); pw.print(callStart);
pw.print(" lastStartId="); pw.println(lastStartId);
+ pw.print(" startCommandResult="); pw.println(startCommandResult);
}
if (executeNesting != 0) {
pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting);
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 060e3ee..2a69363 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -18,6 +18,34 @@
]
},
{
+ "name": "CtsAppFgsTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsShortFgsTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3f8333a..aefa2f5 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -80,7 +80,6 @@
import android.os.IProgressListener;
import android.os.IRemoteCallback;
import android.os.IUserManager;
-import android.os.Looper;
import android.os.Message;
import android.os.PowerWhitelistManager;
import android.os.Process;
@@ -437,6 +436,12 @@
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ /**
+ * Pending user starts waiting for shutdown step to complete.
+ */
+ @GuardedBy("mLock")
+ private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();
+
private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
@@ -944,9 +949,8 @@
int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
- if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
- throw new IllegalArgumentException("Can't stop system user " + userId);
- }
+ Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId);
+
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
@@ -1184,9 +1188,13 @@
} else {
stopped = true;
// User can no longer run.
+ Slogf.i(TAG, "Removing user state from UserController.mStartedUsers for user #"
+ + userId + " as a result of user being stopped");
mStartedUsers.remove(userId);
+
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLU();
+
if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
Slogf.wtf(TAG,
"Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
@@ -1200,7 +1208,10 @@
}
}
if (stopped) {
+ Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId
+ + " as a result of user being stopped");
mInjector.getUserManagerInternal().removeUserState(userId);
+
mInjector.activityManagerOnUserStopped(userId);
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
@@ -1228,10 +1239,13 @@
USER_LIFECYCLE_EVENT_STATE_FINISH);
clearSessionId(userId);
- if (!lockUser) {
- return;
+ if (lockUser) {
+ dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
}
- dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
+
+ // Resume any existing pending user start,
+ // which was paused while the SHUTDOWN flow of the user was in progress.
+ resumePendingUserStarts(userId);
} else {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_NONE);
@@ -1239,6 +1253,31 @@
}
}
+ /**
+ * Resume any existing pending user start for the specified userId which was paused
+ * while the shutdown flow of the user was in progress.
+ * Remove all the handled user starts from mPendingUserStarts.
+ * @param userId the id of the user
+ */
+ private void resumePendingUserStarts(@UserIdInt int userId) {
+ synchronized (mLock) {
+ final List<PendingUserStart> handledUserStarts = new ArrayList<>();
+
+ for (PendingUserStart userStart: mPendingUserStarts) {
+ if (userStart.userId == userId) {
+ Slogf.i(TAG, "resumePendingUserStart for" + userStart);
+ mHandler.post(() -> startUser(userStart.userId,
+ userStart.isForeground, userStart.unlockListener));
+
+ handledUserStarts.add(userStart);
+ }
+ }
+ // remove all the pending user starts which are now handled
+ mPendingUserStarts.removeAll(handledUserStarts);
+ }
+ }
+
+
private void dispatchUserLocking(@UserIdInt int userId,
@Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
// Evict the user's credential encryption key. Performed on FgThread to make it
@@ -1252,6 +1291,7 @@
}
}
try {
+ Slogf.i(TAG, "Locking CE storage for user #" + userId);
mInjector.getStorageManager().lockUserKey(userId);
} catch (RemoteException re) {
throw re.rethrowAsRuntimeException();
@@ -1657,10 +1697,11 @@
updateStartedUserArrayLU();
needStart = true;
updateUmState = true;
- } else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
+ } else if (uss.state == UserState.STATE_SHUTDOWN) {
Slogf.i(TAG, "User #" + userId
- + " is shutting down - will start after full stop");
- mHandler.post(() -> startUser(userId, foreground, unlockListener));
+ + " is shutting down - will start after full shutdown");
+ mPendingUserStarts.add(new PendingUserStart(userId,
+ foreground, unlockListener));
t.traceEnd(); // updateStartedUserArrayStarting
return true;
}
@@ -1818,10 +1859,6 @@
return true;
}
- private boolean isCallingOnHandlerThread() {
- return Looper.myLooper() == mHandler.getLooper();
- }
-
/**
* Start user, if its not already running, and bring it to foreground.
*/
@@ -3390,6 +3427,32 @@
}
}
+ /**
+ * Helper class for keeping track of user starts which are paused while user's
+ * shutdown is taking place.
+ */
+ private static class PendingUserStart {
+ public final @UserIdInt int userId;
+ public final boolean isForeground;
+ public final IProgressListener unlockListener;
+
+ PendingUserStart(int userId, boolean foreground,
+ IProgressListener unlockListener) {
+ this.userId = userId;
+ this.isForeground = foreground;
+ this.unlockListener = unlockListener;
+ }
+
+ @Override
+ public String toString() {
+ return "PendingUserStart{"
+ + "userId=" + userId
+ + ", isForeground=" + isForeground
+ + ", unlockListener=" + unlockListener
+ + '}';
+ }
+ }
+
@VisibleForTesting
static class Injector {
private final ActivityManagerService mService;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 8d1da71..587fb04 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -218,7 +218,7 @@
}
@Override
- public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
+ public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
if (packageModes == null) {
@@ -490,15 +490,16 @@
}
@Override
- public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+ public SparseBooleanArray evalForegroundUidOps(int uid,
+ @Nullable SparseBooleanArray foregroundOps) {
synchronized (mLock) {
return evalForegroundOps(mUidModes.get(uid), foregroundOps);
}
}
@Override
- public SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+ public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+ @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
synchronized (mLock) {
ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
@@ -537,8 +538,8 @@
}
@Override
- public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
- PrintWriter printWriter) {
+ public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+ @NonNull PrintWriter printWriter) {
boolean needSep = false;
if (mOpModeWatchers.size() > 0) {
boolean printedHeader = false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index d8d0d48..ef3e368 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -103,7 +103,7 @@
* @param packageName package name.
* @param userId user id associated with the package.
*/
- boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+ boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
/**
* Stop tracking app-op modes for all uid and packages.
@@ -184,7 +184,7 @@
* @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
* @return foregroundOps.
*/
- SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+ SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
/**
* Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
@@ -194,8 +194,8 @@
* @param userId user id associated with the package.
* @return foregroundOps.
*/
- SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId);
+ SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+ @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
/**
* Dump op mode and package mode listeners and their details.
@@ -205,5 +205,6 @@
* @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
* @param printWriter writer to dump to.
*/
- boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+ boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+ @NonNull PrintWriter printWriter);
}
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 5ebe811..1d1a9e7 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -22,7 +22,7 @@
* Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
* change.
*/
-abstract class OnOpModeChangedListener {
+public abstract class OnOpModeChangedListener {
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8aa898e..aa8ee3d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -86,6 +86,7 @@
import android.media.AudioFocusInfo;
import android.media.AudioFocusRequest;
import android.media.AudioFormat;
+import android.media.AudioHalVersionInfo;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioPlaybackConfiguration;
@@ -257,6 +258,9 @@
/** Debug communication route */
protected static final boolean DEBUG_COMM_RTE = false;
+ /** Debug log sound fx (touchsounds...) in dumpsys */
+ protected static final boolean DEBUG_LOG_SOUND_FX = false;
+
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -378,6 +382,7 @@
private static final int MSG_ROTATION_UPDATE = 48;
private static final int MSG_FOLD_UPDATE = 49;
private static final int MSG_RESET_SPATIALIZER = 50;
+ private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1016,7 +1021,7 @@
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
- mSfxHelper = new SoundEffectsHelper(mContext);
+ mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
final boolean headTrackingDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
@@ -1500,6 +1505,18 @@
}
//-----------------------------------------------------------------
+ // Communicate to PlayackActivityMonitor whether to log or not
+ // the sound FX activity (useful for removing touch sounds in the activity logs)
+ void ignorePlayerLogs(@NonNull PlayerBase playerToIgnore) {
+ if (DEBUG_LOG_SOUND_FX) {
+ return;
+ }
+ sendMsg(mAudioHandler, MSG_NO_LOG_FOR_PLAYER_I, SENDMSG_REPLACE,
+ /*arg1, piid of the player*/ playerToIgnore.getPlayerIId(),
+ /*arg2 ignored*/ 0, /*obj ignored*/ null, /*delay*/ 0);
+ }
+
+ //-----------------------------------------------------------------
// monitoring requests for volume range initialization
@Override // AudioSystemAdapter.OnVolRangeInitRequestListener
public void onVolumeRangeInitRequestFromNative() {
@@ -6622,9 +6639,13 @@
return AudioSystem.STREAM_RING;
} else if (wasStreamActiveRecently(
AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
- return AudioSystem.STREAM_NOTIFICATION;
+ if (DEBUG_VOL) {
+ Log.v(
+ TAG,
+ "getActiveStreamType: Forcing STREAM_NOTIFICATION stream"
+ + " active");
+ }
+ return AudioSystem.STREAM_NOTIFICATION;
} else {
if (DEBUG_VOL) {
Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK("
@@ -8721,6 +8742,10 @@
// fold parameter format: "device_folded=x" where x is one of on, off
mAudioSystem.setParameters((String) msg.obj);
break;
+
+ case MSG_NO_LOG_FOR_PLAYER_I:
+ mPlaybackMonitor.ignorePlayerIId(msg.arg1);
+ break;
}
}
}
@@ -10913,6 +10938,21 @@
}
/**
+ * Called by an AudioPolicyProxy when the client dies.
+ * Checks if an active playback for media use case is currently routed to one of the
+ * remote submix devices owned by this dynamic policy and broadcasts a becoming noisy
+ * intend in this case.
+ * @param addresses list of remote submix device addresses to check.
+ */
+ private void onPolicyClientDeath(List<String> addresses) {
+ for (String address : addresses) {
+ if (mPlaybackMonitor.hasActiveMediaPlaybackOnSubmixWithAddress(address)) {
+ mDeviceBroker.postBroadcastBecomingNoisy();
+ return;
+ }
+ }
+ }
+ /**
* Apps with MODIFY_AUDIO_ROUTING can register any policy.
* Apps with an audio capable MediaProjection are allowed to register a RENDER|LOOPBACK policy
* as those policy do not modify the audio routing.
@@ -11284,15 +11324,16 @@
return mMediaFocusControl.sendFocusLoss(focusLoser);
}
- private static final String[] HAL_VERSIONS =
- new String[] {"7.1", "7.0", "6.0", "5.0", "4.0", "2.0"};
-
- /** @see AudioManager#getHalVersion */
- public @Nullable String getHalVersion() {
- for (String version : HAL_VERSIONS) {
+ /**
+ * @see AudioManager#getHalVersion
+ */
+ public @Nullable AudioHalVersionInfo getHalVersion() {
+ for (AudioHalVersionInfo version : AudioHalVersionInfo.VERSIONS) {
try {
+ // TODO: check AIDL service.
+ String versionStr = version.getMajorVersion() + "." + version.getMinorVersion();
HwBinder.getService(
- String.format("android.hardware.audio@%s::IDevicesFactory", version),
+ String.format("android.hardware.audio@%s::IDevicesFactory", versionStr),
"default");
return version;
} catch (NoSuchElementException e) {
@@ -11622,6 +11663,13 @@
public void binderDied() {
mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
+ mPolicyCallback.asBinder() + " died").printLog(TAG)));
+
+ List<String> addresses = new ArrayList<>();
+ for (AudioMix mix : mMixes) {
+ addresses.add(mix.getRegistration());
+ }
+ onPolicyClientDeath(addresses);
+
release();
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 0bc4b20..f35931ca 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
@@ -191,6 +192,18 @@
}
//=================================================================
+ // Player to ignore (only handling single player, designed for ignoring
+ // in the logs one specific player such as the touch sounds player)
+ @GuardedBy("mPlayerLock")
+ private ArrayList<Integer> mDoNotLogPiidList = new ArrayList<>();
+
+ /*package*/ void ignorePlayerIId(int doNotLogPiid) {
+ synchronized (mPlayerLock) {
+ mDoNotLogPiidList.add(doNotLogPiid);
+ }
+ }
+
+ //=================================================================
// Track players and their states
// methods playerAttributes, playerEvent, releasePlayer are all oneway calls
// into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
@@ -314,14 +327,18 @@
Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
}
-
- final boolean change;
+ boolean change;
synchronized(mPlayerLock) {
final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
if (apc == null) {
return;
}
+ final boolean doNotLog = mDoNotLogPiidList.contains(piid);
+ if (doNotLog && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+ // do not log nor dispatch events for "ignored" players other than the release
+ return;
+ }
sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
@@ -338,7 +355,8 @@
}
}
}
- if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+ && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
// FIXME SoundPool not ready for state reporting
return;
}
@@ -350,9 +368,15 @@
Log.e(TAG, "Error handling event " + event);
change = false;
}
- if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
- mDuckingManager.checkDuck(apc);
- mFadingManager.checkFade(apc);
+ if (change) {
+ if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ mDuckingManager.checkDuck(apc);
+ mFadingManager.checkFade(apc);
+ }
+ if (doNotLog) {
+ // do not dispatch events for "ignored" players
+ change = false;
+ }
}
}
if (change) {
@@ -435,6 +459,10 @@
mEventHandler.sendMessage(
mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
+ if (change && mDoNotLogPiidList.contains(piid)) {
+ // do not dispatch a change for a "do not log" player
+ change = false;
+ }
}
}
if (change) {
@@ -542,6 +570,26 @@
return false;
}
+ /**
+ * Return true if an active playback for media use case is currently routed to
+ * a remote submix device with the supplied address.
+ * @param address
+ */
+ public boolean hasActiveMediaPlaybackOnSubmixWithAddress(@NonNull String address) {
+ synchronized (mPlayerLock) {
+ for (AudioPlaybackConfiguration apc : mPlayers.values()) {
+ AudioDeviceInfo device = apc.getAudioDeviceInfo();
+ if (apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_MEDIA
+ && apc.isActive() && device != null
+ && device.getInternalType() == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
+ && address.equals(device.getAddress())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
protected void dump(PrintWriter pw) {
// players
pw.println("\nPlaybackActivityMonitor dump time: "
@@ -560,6 +608,9 @@
for (Integer piidInt : piidIntList) {
final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
if (apc != null) {
+ if (mDoNotLogPiidList.contains(apc.getPlayerInterfaceId())) {
+ pw.print("(not logged)");
+ }
apc.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 79b54eb..8c4efba 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -25,6 +25,7 @@
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
import android.media.SoundPool;
import android.os.Environment;
import android.os.Handler;
@@ -47,6 +48,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
/**
* A helper class for managing sound effects loading / unloading
@@ -109,11 +111,14 @@
private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
private SoundPool mSoundPool;
private SoundPoolLoader mSoundPoolLoader;
+ /** callback to provide handle to the player of the sound effects */
+ private final Consumer<PlayerBase> mPlayerAvailableCb;
- SoundEffectsHelper(Context context) {
+ SoundEffectsHelper(Context context, Consumer<PlayerBase> playerAvailableCb) {
mContext = context;
mSfxAttenuationDb = mContext.getResources().getInteger(
com.android.internal.R.integer.config_soundEffectVolumeDb);
+ mPlayerAvailableCb = playerAvailableCb;
startWorker();
}
@@ -189,6 +194,7 @@
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build())
.build();
+ mPlayerAvailableCb.accept(mSoundPool);
loadSoundAssets();
mSoundPoolLoader = new SoundPoolLoader();
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index eb81e70..dcc98e1 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -27,6 +27,7 @@
import android.accounts.AccountManagerInternal;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
@@ -65,6 +66,7 @@
import android.content.pm.RegisteredServicesCacheListener;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.database.ContentObserver;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
@@ -88,6 +90,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
+import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.TimeMigrationUtils;
@@ -99,6 +102,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -498,7 +502,7 @@
}
mJobScheduler = (JobScheduler) mContext.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
- mJobSchedulerInternal = LocalServices.getService(JobSchedulerInternal.class);
+ mJobSchedulerInternal = getJobSchedulerInternal();
// Get all persisted syncs from JobScheduler
List<JobInfo> pendingJobs = mJobScheduler.getAllPendingJobs();
@@ -536,6 +540,11 @@
}
}
+ @VisibleForTesting
+ protected JobSchedulerInternal getJobSchedulerInternal() {
+ return LocalServices.getService(JobSchedulerInternal.class);
+ }
+
/**
* @return whether the device most likely has some periodic syncs.
*/
@@ -645,7 +654,7 @@
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mAccountManager = (AccountManager) mContext.getSystemService(Context.ACCOUNT_SERVICE);
- mAccountManagerInternal = LocalServices.getService(AccountManagerInternal.class);
+ mAccountManagerInternal = getAccountManagerInternal();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mAmi = LocalServices.getService(ActivityManagerInternal.class);
@@ -719,6 +728,11 @@
mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
}
+ @VisibleForTesting
+ protected AccountManagerInternal getAccountManagerInternal() {
+ return LocalServices.getService(AccountManagerInternal.class);
+ }
+
public void onStartUser(int userId) {
// Log on the handler to avoid slowing down device boot.
mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userId));
@@ -800,9 +814,44 @@
return mSyncStorageEngine;
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private boolean areContactWritesEnabledForUser(UserInfo userInfo) {
+ final UserManager um = UserManager.get(mContext);
+ try {
+ final UserProperties userProperties = um.getUserProperties(userInfo.getUserHandle());
+ return !userProperties.getUseParentsContacts();
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Trying to fetch user properties for non-existing/partial user "
+ + userInfo.getUserHandle());
+ return false;
+ }
+ }
+
+ /**
+ * Check if account sync should be disabled for the given user and provider.
+ * @param userInfo
+ * @param providerName
+ * @return true if sync for the account corresponding to the given user and provider should be
+ * disabled, false otherwise. Also returns false if either of the inputs are null.
+ */
+ @VisibleForTesting
+ protected boolean shouldDisableSyncForUser(UserInfo userInfo, String providerName) {
+ if (userInfo == null || providerName == null) return false;
+ return providerName.equals(ContactsContract.AUTHORITY)
+ && !areContactWritesEnabledForUser(userInfo);
+ }
+
private int getIsSyncable(Account account, int userId, String providerName) {
int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName);
- UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
+ final UserManager um = UserManager.get(mContext);
+ UserInfo userInfo = um.getUserInfo(userId);
+
+ // Check if the provider is allowed to sync data from linked accounts for the user
+ if (shouldDisableSyncForUser(userInfo, providerName)) {
+ Log.w(TAG, "Account sync is disabled for account: " + account
+ + " userId: " + userId + " provider: " + providerName);
+ return AuthorityInfo.NOT_SYNCABLE;
+ }
// If it's not a restricted user, return isSyncable.
if (userInfo == null || !userInfo.isRestricted()) return isSyncable;
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index d499d01..197c64e 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -602,6 +602,14 @@
mAmbientBrightnessThresholdsIdle.dump(pw);
}
+ public float[] getLastSensorValues() {
+ return mAmbientLightRingBuffer.getAllLuxValues();
+ }
+
+ public long[] getLastSensorTimestamps() {
+ return mAmbientLightRingBuffer.getAllTimestamps();
+ }
+
private String configStateToString(int state) {
switch (state) {
case AUTO_BRIGHTNESS_ENABLED:
@@ -1231,10 +1239,42 @@
return mRingLux[offsetOf(index)];
}
+ public float[] getAllLuxValues() {
+ float[] values = new float[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingLux, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingLux, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingLux, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
public long getTime(int index) {
return mRingTime[offsetOf(index)];
}
+ public long[] getAllTimestamps() {
+ long[] values = new long[mCount];
+ if (mCount == 0) {
+ return values;
+ }
+
+ if (mStart < mEnd) {
+ System.arraycopy(mRingTime, mStart, values, 0, mCount);
+ } else {
+ System.arraycopy(mRingTime, mStart, values, 0, mCapacity - mStart);
+ System.arraycopy(mRingTime, 0, values, mCapacity - mStart, mEnd);
+ }
+
+ return values;
+ }
+
public void push(long time, float lux) {
int next = mEnd;
if (mCount == mCapacity) {
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index df4c471..6e1640d 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -79,10 +79,8 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
-import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -101,8 +99,6 @@
private static final int MAX_EVENTS = 100;
// Discard events when reading or writing that are older than this.
private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30);
- // Time over which we keep lux sensor readings.
- private static final long LUX_EVENT_HORIZON = TimeUnit.SECONDS.toNanos(10);
private static final String TAG_EVENTS = "events";
private static final String TAG_EVENT = "event";
@@ -174,8 +170,6 @@
// Lock held while collecting data related to brightness changes.
private final Object mDataCollectionLock = new Object();
@GuardedBy("mDataCollectionLock")
- private Deque<LightData> mLastSensorReadings = new ArrayDeque<>();
- @GuardedBy("mDataCollectionLock")
private float mLastBatteryLevel = Float.NaN;
@GuardedBy("mDataCollectionLock")
private float mLastBrightness = -1;
@@ -327,7 +321,8 @@
*/
public void notifyBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig, String uniqueDisplayId) {
+ boolean isDefaultBrightnessConfig, String uniqueDisplayId, float[] luxValues,
+ long[] luxTimestamps) {
if (DEBUG) {
Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)",
brightness, userInitiated));
@@ -335,7 +330,7 @@
Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED,
userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness,
powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig,
- mInjector.currentTimeMillis(), uniqueDisplayId));
+ mInjector.currentTimeMillis(), uniqueDisplayId, luxValues, luxTimestamps));
m.sendToTarget();
}
@@ -349,7 +344,8 @@
private void handleBrightnessChanged(float brightness, boolean userInitiated,
float powerBrightnessFactor, boolean isUserSetBrightness,
- boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId) {
+ boolean isDefaultBrightnessConfig, long timestamp, String uniqueDisplayId,
+ float[] luxValues, long[] luxTimestamps) {
BrightnessChangeEvent.Builder builder;
synchronized (mDataCollectionLock) {
@@ -376,28 +372,22 @@
builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig);
builder.setUniqueDisplayId(uniqueDisplayId);
- final int readingCount = mLastSensorReadings.size();
- if (readingCount == 0) {
+ if (luxValues.length == 0) {
// No sensor data so ignore this.
return;
}
- float[] luxValues = new float[readingCount];
- long[] luxTimestamps = new long[readingCount];
+ long[] luxTimestampsMillis = new long[luxTimestamps.length];
- int pos = 0;
-
- // Convert sensor timestamp in elapsed time nanos to current time millis.
+ // Convert lux timestamp in elapsed time to current time.
long currentTimeMillis = mInjector.currentTimeMillis();
long elapsedTimeNanos = mInjector.elapsedRealtimeNanos();
- for (LightData reading : mLastSensorReadings) {
- luxValues[pos] = reading.lux;
- luxTimestamps[pos] = currentTimeMillis -
- TimeUnit.NANOSECONDS.toMillis(elapsedTimeNanos - reading.timestamp);
- ++pos;
+ for (int i = 0; i < luxTimestamps.length; i++) {
+ luxTimestampsMillis[i] = currentTimeMillis - (TimeUnit.NANOSECONDS.toMillis(
+ elapsedTimeNanos) - luxTimestamps[i]);
}
builder.setLuxValues(luxValues);
- builder.setLuxTimestamps(luxTimestamps);
+ builder.setLuxTimestamps(luxTimestampsMillis);
builder.setBatteryLevel(mLastBatteryLevel);
builder.setLastBrightness(previousBrightness);
@@ -452,9 +442,6 @@
if (mLightSensor != lightSensor) {
mLightSensor = lightSensor;
stopSensorListener();
- synchronized (mDataCollectionLock) {
- mLastSensorReadings.clear();
- }
// Attempt to restart the sensor listener. It will check to see if it should be running
// so there is no need to also check here.
startSensorListener();
@@ -798,12 +785,6 @@
pw.println(" mLightSensor=" + mLightSensor);
pw.println(" mLastBatteryLevel=" + mLastBatteryLevel);
pw.println(" mLastBrightness=" + mLastBrightness);
- pw.println(" mLastSensorReadings.size=" + mLastSensorReadings.size());
- if (!mLastSensorReadings.isEmpty()) {
- pw.println(" mLastSensorReadings time span "
- + mLastSensorReadings.peekFirst().timestamp + "->"
- + mLastSensorReadings.peekLast().timestamp);
- }
}
synchronized (mEventsLock) {
pw.println(" mEventsDirty=" + mEventsDirty);
@@ -919,43 +900,6 @@
return ParceledListSlice.emptyList();
}
- // Not allowed to keep the SensorEvent so used to copy the data we care about.
- private static class LightData {
- public float lux;
- // Time in elapsedRealtimeNanos
- public long timestamp;
- }
-
- private void recordSensorEvent(SensorEvent event) {
- long horizon = mInjector.elapsedRealtimeNanos() - LUX_EVENT_HORIZON;
- synchronized (mDataCollectionLock) {
- if (DEBUG) {
- Slog.v(TAG, "Sensor event " + event);
- }
- if (!mLastSensorReadings.isEmpty()
- && event.timestamp < mLastSensorReadings.getLast().timestamp) {
- // Ignore event that came out of order.
- return;
- }
- LightData data = null;
- while (!mLastSensorReadings.isEmpty()
- && mLastSensorReadings.getFirst().timestamp < horizon) {
- // Remove data that has fallen out of the window.
- data = mLastSensorReadings.removeFirst();
- }
- // We put back the last one we removed so we know how long
- // the first sensor reading was valid for.
- if (data != null) {
- mLastSensorReadings.addFirst(data);
- }
-
- data = new LightData();
- data.timestamp = event.timestamp;
- data.lux = event.values[0];
- mLastSensorReadings.addLast(data);
- }
- }
-
private void recordAmbientBrightnessStats(SensorEvent event) {
mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]);
}
@@ -969,7 +913,6 @@
private final class SensorListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {
- recordSensorEvent(event);
recordAmbientBrightnessStats(event);
}
@@ -1056,7 +999,7 @@
handleBrightnessChanged(values.brightness, userInitiatedChange,
values.powerBrightnessFactor, values.isUserSetBrightness,
values.isDefaultBrightnessConfig, values.timestamp,
- values.uniqueDisplayId);
+ values.uniqueDisplayId, values.luxValues, values.luxTimestamps);
break;
case MSG_START_SENSOR_LISTENER:
startSensorListener();
@@ -1092,16 +1035,20 @@
public final boolean isDefaultBrightnessConfig;
public final long timestamp;
public final String uniqueDisplayId;
+ public final float[] luxValues;
+ public final long[] luxTimestamps;
BrightnessChangeValues(float brightness, float powerBrightnessFactor,
boolean isUserSetBrightness, boolean isDefaultBrightnessConfig,
- long timestamp, String uniqueDisplayId) {
+ long timestamp, String uniqueDisplayId, float[] luxValues, long[] luxTimestamps) {
this.brightness = brightness;
this.powerBrightnessFactor = powerBrightnessFactor;
this.isUserSetBrightness = isUserSetBrightness;
this.isDefaultBrightnessConfig = isDefaultBrightnessConfig;
this.timestamp = timestamp;
this.uniqueDisplayId = uniqueDisplayId;
+ this.luxValues = luxValues;
+ this.luxTimestamps = luxTimestamps;
}
}
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 9dd2f84..fc6403d 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import android.annotation.NonNull;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Environment;
import android.util.IndentingPrintWriter;
@@ -23,8 +24,10 @@
import android.util.SparseArray;
import android.view.DisplayAddress;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.config.layout.Layouts;
import com.android.server.display.config.layout.XmlParser;
+import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import org.xmlpull.v1.XmlPullParserException;
@@ -48,13 +51,28 @@
public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE;
+ // Direction of the display relative to the default display, whilst in this state
+ private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN;
+ private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT;
+ private static final int POSITION_REAR = Layout.Display.POSITION_REAR;
+
+ private static final String FRONT_STRING = "front";
+ private static final String REAR_STRING = "rear";
+
private static final String CONFIG_FILE_PATH =
"etc/displayconfig/display_layout_configuration.xml";
private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
+ private final DisplayIdProducer mIdProducer;
- DeviceStateToLayoutMap() {
- loadLayoutsFromConfig();
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer) {
+ this(idProducer, Environment.buildPath(
+ Environment.getVendorDirectory(), CONFIG_FILE_PATH));
+ }
+
+ DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) {
+ mIdProducer = idProducer;
+ loadLayoutsFromConfig(configFile);
createLayout(STATE_DEFAULT);
}
@@ -76,24 +94,11 @@
return layout;
}
- private Layout createLayout(int state) {
- if (mLayoutMap.contains(state)) {
- Slog.e(TAG, "Attempted to create a second layout for state " + state);
- return null;
- }
-
- final Layout layout = new Layout();
- mLayoutMap.append(state, layout);
- return layout;
- }
-
/**
* Reads display-layout-configuration files to get the layouts to use for this device.
*/
- private void loadLayoutsFromConfig() {
- final File configFile = Environment.buildPath(
- Environment.getVendorDirectory(), CONFIG_FILE_PATH);
-
+ @VisibleForTesting
+ void loadLayoutsFromConfig(@NonNull File configFile) {
if (!configFile.exists()) {
return;
}
@@ -109,10 +114,19 @@
final int state = l.getState().intValue();
final Layout layout = createLayout(state);
for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
- layout.createDisplayLocked(
+ Layout.Display display = layout.createDisplayLocked(
DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
d.isDefaultDisplay(),
- d.isEnabled());
+ d.isEnabled(),
+ mIdProducer);
+
+ if (FRONT_STRING.equals(d.getPosition())) {
+ display.setPosition(POSITION_FRONT);
+ } else if (REAR_STRING.equals(d.getPosition())) {
+ display.setPosition(POSITION_REAR);
+ } else {
+ display.setPosition(POSITION_UNKNOWN);
+ }
}
}
} catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
@@ -120,4 +134,15 @@
+ configFile, e);
}
}
+
+ private Layout createLayout(int state) {
+ if (mLayoutMap.contains(state)) {
+ Slog.e(TAG, "Attempted to create a second layout for state " + state);
+ return null;
+ }
+
+ final Layout layout = new Layout();
+ mLayoutMap.append(state, layout);
+ return layout;
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 1fc15122..4f1df3f 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -120,13 +120,14 @@
}
public static Display.Mode createMode(int width, int height, float refreshRate) {
- return createMode(width, height, refreshRate, new float[0]);
+ return createMode(width, height, refreshRate, new float[0], new int[0]);
}
public static Display.Mode createMode(int width, int height, float refreshRate,
- float[] alternativeRefreshRates) {
+ float[] alternativeRefreshRates,
+ @Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- alternativeRefreshRates);
+ alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 8811999..fe1d1a6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -145,7 +145,7 @@
/**
* Flag: Indicates that the display should always be unlocked. Only valid on virtual displays
* that aren't in the default display group.
- * @see #FLAG_OWN_DISPLAY_GROUP
+ * @see #FLAG_OWN_DISPLAY_GROUP and #FLAG_DEVICE_DISPLAY_GROUP
* @hide
*/
public static final int FLAG_ALWAYS_UNLOCKED = 1 << 15;
@@ -172,6 +172,14 @@
public static final int FLAG_OWN_FOCUS = 1 << 17;
/**
+ * Flag: indicates that the display should not be a part of the default {@link DisplayGroup} and
+ * instead be part of a {@link DisplayGroup} associated with the Virtual Device.
+ *
+ * @hide
+ */
+ public static final int FLAG_DEVICE_DISPLAY_GROUP = 1 << 18;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -237,6 +245,12 @@
public int modeId;
/**
+ * The render frame rate this display is scheduled at.
+ * @see android.view.DisplayInfo#renderFrameRate for more details.
+ */
+ public float renderFrameRate;
+
+ /**
* The default mode of the display.
*/
public int defaultModeId;
@@ -431,6 +445,7 @@
|| width != other.width
|| height != other.height
|| modeId != other.modeId
+ || renderFrameRate != other.renderFrameRate
|| defaultModeId != other.defaultModeId
|| !Arrays.equals(supportedModes, other.supportedModes)
|| !Arrays.equals(supportedColorModes, other.supportedColorModes)
@@ -475,6 +490,7 @@
width = other.width;
height = other.height;
modeId = other.modeId;
+ renderFrameRate = other.renderFrameRate;
defaultModeId = other.defaultModeId;
supportedModes = other.supportedModes;
colorMode = other.colorMode;
@@ -515,6 +531,7 @@
sb.append(name).append("\": uniqueId=\"").append(uniqueId).append("\", ");
sb.append(width).append(" x ").append(height);
sb.append(", modeId ").append(modeId);
+ sb.append(", renderFrameRate ").append(renderFrameRate);
sb.append(", defaultModeId ").append(defaultModeId);
sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
sb.append(", colorMode ").append(colorMode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 1d04f2e..ae84e96 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -25,6 +25,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
@@ -35,6 +36,7 @@
import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.ROOT_UID;
import android.Manifest;
@@ -881,20 +883,27 @@
private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
frameRateOverrides, DisplayInfo info, int callingUid) {
- float frameRateHz = 0;
+ float frameRateHz = info.renderFrameRate;
for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
if (frameRateOverride.uid == callingUid) {
frameRateHz = frameRateOverride.frameRateHz;
break;
}
}
+
if (frameRateHz == 0) {
return info;
}
+ // For non-apps users we always return the physical refresh rate from display mode
+ boolean displayModeReturnsPhysicalRefreshRate =
+ callingUid < FIRST_APPLICATION_UID
+ || CompatChanges.isChangeEnabled(
+ DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
+
// Override the refresh rate only if it is a divisor of the current
// refresh rate. This calculation needs to be in sync with the native code
- // in RefreshRateConfigs::getFrameRateDivisor
+ // in RefreshRateSelector::getFrameRateDivisor
Display.Mode currentMode = info.getMode();
float numPeriods = currentMode.getRefreshRate() / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
@@ -918,8 +927,7 @@
}
overriddenInfo.refreshRateOverride = mode.getRefreshRate();
- if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
- callingUid)) {
+ if (!displayModeReturnsPhysicalRefreshRate) {
overriddenInfo.modeId = mode.getModeId();
}
return overriddenInfo;
@@ -927,14 +935,14 @@
}
overriddenInfo.refreshRateOverride = frameRateHz;
- if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
- callingUid)) {
+ if (!displayModeReturnsPhysicalRefreshRate) {
overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
info.supportedModes.length + 1);
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
- overriddenInfo.refreshRateOverride);
+ overriddenInfo.refreshRateOverride,
+ new float[0], currentMode.getSupportedHdrTypes());
overriddenInfo.modeId =
overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
.getModeId();
@@ -1275,6 +1283,9 @@
if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
}
+ if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0 && virtualDevice != null) {
+ flags |= VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
+ }
if (projection != null) {
try {
@@ -1402,7 +1413,7 @@
// If the display is to be added to a device display group, we need to make the
// LogicalDisplayMapper aware of the link between the new display and its associated virtual
// device before triggering DISPLAY_DEVICE_EVENT_ADDED.
- if (virtualDevice != null && (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) == 0) {
+ if ((flags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
try {
final int virtualDeviceId = virtualDevice.getDeviceId();
mLogicalDisplayMapper.associateDisplayDeviceWithVirtualDevice(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index de26abc..9d47892 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2435,7 +2435,9 @@
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
powerFactor, hadUserDataPoint,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+ mAutomaticBrightnessController.getLastSensorValues(),
+ mAutomaticBrightnessController.getLastSensorTimestamps());
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 1f9df9e..346b340 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -291,7 +291,6 @@
private boolean mAppliedDimming;
private boolean mAppliedLowPower;
private boolean mAppliedTemporaryAutoBrightnessAdjustment;
- private boolean mAppliedBrightnessBoost;
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -1202,18 +1201,6 @@
brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
mAppliedTemporaryAutoBrightnessAdjustment = false;
}
- // Apply brightness boost.
- // We do this here after deciding whether auto-brightness is enabled so that we don't
- // disable the light sensor during this temporary state. That way when boost ends we will
- // be able to resume normal auto-brightness behavior without any delay.
- if (mPowerRequest.boostScreenBrightness
- && brightnessState != PowerManager.BRIGHTNESS_OFF_FLOAT) {
- brightnessState = PowerManager.BRIGHTNESS_MAX;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_BOOST);
- mAppliedBrightnessBoost = true;
- } else {
- mAppliedBrightnessBoost = false;
- }
// If the brightness is already set then it's been overridden by something other than the
// user, or is a temporary adjustment.
@@ -2154,7 +2141,9 @@
: 1.0f;
mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated,
powerFactor, hadUserDataPoint,
- mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId);
+ mAutomaticBrightnessController.isDefaultConfig(), mUniqueDisplayId,
+ mAutomaticBrightnessController.getLastSensorValues(),
+ mAutomaticBrightnessController.getLastSensorTimestamps());
}
}
@@ -2227,7 +2216,6 @@
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ mAppliedTemporaryAutoBrightnessAdjustment);
- pw.println(" mAppliedBrightnessBoost=" + mAppliedBrightnessBoost);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index f85b990..ee53b60 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -226,6 +226,8 @@
private SurfaceControl.DisplayMode[] mSfDisplayModes;
// The active display mode in SurfaceFlinger
private SurfaceControl.DisplayMode mActiveSfDisplayMode;
+ // The active display vsync period in SurfaceFlinger
+ private float mActiveRenderFrameRate;
private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
new DisplayEventReceiver.FrameRateOverride[0];
@@ -267,7 +269,7 @@
SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
boolean changed = updateDisplayModesLocked(
dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode,
- dynamicInfo.activeDisplayModeId, modeSpecs);
+ dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs);
changed |= updateStaticInfo(staticInfo);
changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
dynamicInfo.activeColorMode);
@@ -283,7 +285,8 @@
public boolean updateDisplayModesLocked(
SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId,
- int activeSfDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
+ int activeSfDisplayModeId, float renderFrameRate,
+ SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId);
SurfaceControl.DisplayMode preferredSfDisplayMode =
@@ -379,6 +382,16 @@
sendTraversalRequestLocked();
}
+ boolean renderFrameRateChanged = false;
+
+ if (mActiveRenderFrameRate > 0 && mActiveRenderFrameRate != renderFrameRate) {
+ Slog.d(TAG, "The render frame rate was changed from SurfaceFlinger or the display"
+ + " device to " + renderFrameRate);
+ mActiveRenderFrameRate = renderFrameRate;
+ renderFrameRateChanged = true;
+ sendTraversalRequestLocked();
+ }
+
// Check whether surface flinger spontaneously changed display config specs out from
// under us. If so, schedule a traversal to reapply our display config specs.
if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) {
@@ -398,7 +411,7 @@
boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
// If the records haven't changed then we're done here.
if (!recordsChanged) {
- return activeModeChanged || preferredModeChanged;
+ return activeModeChanged || preferredModeChanged || renderFrameRateChanged;
}
mSupportedModes.clear();
@@ -410,16 +423,19 @@
if (mDefaultModeId == INVALID_MODE_ID) {
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mActiveRenderFrameRate = renderFrameRate;
} else if (modesAdded && activeModeChanged) {
Slog.d(TAG, "New display modes are added and the active mode has changed, "
+ "use active mode as default mode.");
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mActiveRenderFrameRate = renderFrameRate;
} else if (findSfDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
Slog.w(TAG, "Default display mode no longer available, using currently"
+ " active mode as default.");
mDefaultModeId = activeRecord.mMode.getModeId();
mDefaultModeGroup = mActiveSfDisplayMode.group;
+ mActiveRenderFrameRate = renderFrameRate;
}
// Determine whether the display mode specs' base mode is still there.
@@ -584,7 +600,9 @@
DisplayModeRecord record = mSupportedModes.valueAt(i);
if (record.hasMatchingMode(mode)
&& refreshRatesEquals(alternativeRefreshRates,
- record.mMode.getAlternativeRefreshRates())) {
+ record.mMode.getAlternativeRefreshRates())
+ && hdrTypesEqual(mode.supportedHdrTypes,
+ record.mMode.getSupportedHdrTypes())) {
return record;
}
}
@@ -618,6 +636,7 @@
mInfo.width = mActiveSfDisplayMode.width;
mInfo.height = mActiveSfDisplayMode.height;
mInfo.modeId = mActiveModeId;
+ mInfo.renderFrameRate = mActiveRenderFrameRate;
mInfo.defaultModeId = getPreferredModeId();
mInfo.supportedModes = getDisplayModes(mSupportedModes);
mInfo.colorMode = mActiveColorMode;
@@ -993,8 +1012,8 @@
updateDeviceInfoLocked();
}
- public void onActiveDisplayModeChangedLocked(int sfModeId) {
- if (updateActiveModeLocked(sfModeId)) {
+ public void onActiveDisplayModeChangedLocked(int sfModeId, float renderFrameRate) {
+ if (updateActiveModeLocked(sfModeId, renderFrameRate)) {
updateDeviceInfoLocked();
}
}
@@ -1006,8 +1025,9 @@
}
}
- public boolean updateActiveModeLocked(int activeSfModeId) {
- if (mActiveSfDisplayMode.id == activeSfModeId) {
+ public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
+ if (mActiveSfDisplayMode.id == activeSfModeId
+ && mActiveRenderFrameRate == renderFrameRate) {
return false;
}
mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
@@ -1016,6 +1036,7 @@
Slog.w(TAG, "In unknown mode after setting allowed modes"
+ ", activeModeId=" + activeSfModeId);
}
+ mActiveRenderFrameRate = renderFrameRate;
return true;
}
@@ -1112,6 +1133,7 @@
pw.println(" " + sfDisplayMode);
}
pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
+ pw.println("mActiveRenderFrameRate=" + mActiveRenderFrameRate);
pw.println("mSupportedModes=");
for (int i = 0; i < mSupportedModes.size(); i++) {
pw.println(" " + mSupportedModes.valueAt(i));
@@ -1226,6 +1248,13 @@
}
}
+ private boolean hdrTypesEqual(int[] modeHdrTypes, int[] recordHdrTypes) {
+ int[] modeHdrTypesCopy = Arrays.copyOf(modeHdrTypes, modeHdrTypes.length);
+ Arrays.sort(modeHdrTypesCopy);
+ // Record HDR types are already sorted when we create the DisplayModeRecord
+ return Arrays.equals(modeHdrTypesCopy, recordHdrTypes);
+ }
+
/** Supplies a context whose Resources apply runtime-overlays */
Context getOverlayContext() {
if (mOverlayContext == null) {
@@ -1243,7 +1272,7 @@
DisplayModeRecord(SurfaceControl.DisplayMode mode,
float[] alternativeRefreshRates) {
mMode = createMode(mode.width, mode.height, mode.refreshRate,
- alternativeRefreshRates);
+ alternativeRefreshRates, mode.supportedHdrTypes);
}
/**
@@ -1257,7 +1286,7 @@
return mMode.getPhysicalWidth() == mode.width
&& mMode.getPhysicalHeight() == mode.height
&& Float.floatToIntBits(mMode.getRefreshRate())
- == Float.floatToIntBits(mode.refreshRate);
+ == Float.floatToIntBits(mode.refreshRate);
}
public String toString() {
@@ -1279,7 +1308,8 @@
public interface DisplayEventListener {
void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
- void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId);
+ void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod);
void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
DisplayEventReceiver.FrameRateOverride[] overrides);
@@ -1300,8 +1330,9 @@
}
@Override
- public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
- mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId);
+ public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
+ mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
}
@Override
@@ -1324,12 +1355,14 @@
}
@Override
- public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+ public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+ long renderPeriod) {
if (DEBUG) {
Slog.d(TAG, "onModeChanged("
+ "timestampNanos=" + timestampNanos
+ ", physicalDisplayId=" + physicalDisplayId
- + ", modeId=" + modeId + ")");
+ + ", modeId=" + modeId
+ + ", renderPeriod=" + renderPeriod + ")");
}
synchronized (getSyncRoot()) {
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
@@ -1340,7 +1373,8 @@
}
return;
}
- device.onActiveDisplayModeChangedLocked(modeId);
+ float renderFrameRate = 1e9f / renderPeriod;
+ device.onActiveDisplayModeChangedLocked(modeId, renderFrameRate);
}
}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8dd169bf..c7b27de 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -377,6 +377,7 @@
mBaseDisplayInfo.logicalHeight = maskedHeight;
mBaseDisplayInfo.rotation = Surface.ROTATION_0;
mBaseDisplayInfo.modeId = deviceInfo.modeId;
+ mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
mBaseDisplayInfo.supportedModes = Arrays.copyOf(
deviceInfo.supportedModes, deviceInfo.supportedModes.length);
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 66073c2..80f47a1 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -40,6 +40,7 @@
import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import java.io.PrintWriter;
@@ -82,6 +83,8 @@
private static final int UPDATE_STATE_TRANSITION = 1;
private static final int UPDATE_STATE_UPDATED = 2;
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
+
/**
* Temporary display info, used for comparing display configurations.
*/
@@ -170,6 +173,8 @@
private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>();
private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ private final DisplayIdProducer mIdProducer = (isDefault) ->
+ isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
private Layout mCurrentLayout = null;
private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE;
@@ -179,7 +184,9 @@
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
@NonNull Handler handler) {
- this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap());
+ this(context, repo, listener, syncRoot, handler,
+ new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
+ : sNextNonDefaultDisplayId++));
}
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@@ -588,7 +595,7 @@
// Create a logical display for the new display device
LogicalDisplay display = createNewLogicalDisplayLocked(
- device, Layout.assignDisplayIdLocked(false /*isDefault*/));
+ device, mIdProducer.getId(/* isDefault= */ false));
applyLayoutLocked();
updateLogicalDisplaysLocked();
@@ -621,7 +628,7 @@
& DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0
&& !nextDeviceInfo.address.equals(deviceInfo.address)) {
layout.createDisplayLocked(nextDeviceInfo.address,
- /* isDefault= */ true, /* isEnabled= */ true);
+ /* isDefault= */ true, /* isEnabled= */ true, mIdProducer);
applyLayoutLocked();
return;
}
@@ -1036,7 +1043,8 @@
return;
}
final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
- layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true);
+ layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true,
+ mIdProducer);
}
private int assignLayerStackLocked(int displayId) {
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 0e11b53..3e67f0a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -340,6 +340,7 @@
mInfo.width = mode.getPhysicalWidth();
mInfo.height = mode.getPhysicalHeight();
mInfo.modeId = mode.getModeId();
+ mInfo.renderFrameRate = mode.getRefreshRate();
mInfo.defaultModeId = mModes[0].getModeId();
mInfo.supportedModes = mModes;
mInfo.densityDpi = rawMode.mDensityDpi;
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index f30a84f..e7601bc 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -619,7 +619,7 @@
private static final class DisplayState {
private int mColorMode;
- private float mBrightness;
+ private float mBrightness = Float.NaN;
private int mWidth;
private int mHeight;
private float mRefreshRate;
@@ -700,7 +700,11 @@
break;
case TAG_BRIGHTNESS_VALUE:
String brightness = parser.nextText();
- mBrightness = Float.parseFloat(brightness);
+ try {
+ mBrightness = Float.parseFloat(brightness);
+ } catch (NumberFormatException e) {
+ mBrightness = Float.NaN;
+ }
break;
case TAG_BRIGHTNESS_CONFIGURATIONS:
mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -727,7 +731,9 @@
serializer.endTag(null, TAG_COLOR_MODE);
serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
- serializer.text(Float.toString(mBrightness));
+ if (!Float.isNaN(mBrightness)) {
+ serializer.text(Float.toString(mBrightness));
+ }
serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d0e518b..a118b2f 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -20,6 +20,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
@@ -32,6 +33,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED;
import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
@@ -446,6 +448,7 @@
mInfo.width = mWidth;
mInfo.height = mHeight;
mInfo.modeId = mMode.getModeId();
+ mInfo.renderFrameRate = mMode.getRefreshRate();
mInfo.defaultModeId = mMode.getModeId();
mInfo.supportedModes = new Display.Mode[] { mMode };
mInfo.densityDpi = mDensityDpi;
@@ -466,6 +469,9 @@
mInfo.flags |= FLAG_OWN_DISPLAY_GROUP;
}
}
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
+ mInfo.flags |= FLAG_DEVICE_DISPLAY_GROUP;
+ }
if ((mFlags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
@@ -498,11 +504,15 @@
mInfo.flags |= FLAG_TRUSTED;
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
+ if ((mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0
+ || (mFlags & VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP) != 0) {
mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
} else {
- Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it "
- + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+ Slog.w(
+ TAG,
+ "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it requires"
+ + " VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP or"
+ + " VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
}
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index c759d98..e832701 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -646,6 +646,7 @@
mInfo.width = mWidth;
mInfo.height = mHeight;
mInfo.modeId = mMode.getModeId();
+ mInfo.renderFrameRate = mMode.getRefreshRate();
mInfo.defaultModeId = mMode.getModeId();
mInfo.supportedModes = new Display.Mode[] { mMode };
mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 4759b7d..7d05f13 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -25,6 +25,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
@@ -52,6 +53,8 @@
private final OverrideBrightnessStrategy mOverrideBrightnessStrategy;
// The brightness strategy used to manage the brightness state in temporary state
private final TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
+ // The brightness strategy used to manage the brightness state when boost is requested
+ private final BoostBrightnessStrategy mBoostBrightnessStrategy;
// The brightness strategy used to manage the brightness state when the request is invalid.
private final InvalidBrightnessStrategy mInvalidBrightnessStrategy;
@@ -72,6 +75,7 @@
mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
mOverrideBrightnessStrategy = injector.getOverrideBrightnessStrategy();
mTemporaryBrightnessStrategy = injector.getTemporaryBrightnessStrategy();
+ mBoostBrightnessStrategy = injector.getBoostBrightnessStrategy();
mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy();
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
@@ -89,6 +93,8 @@
DisplayBrightnessStrategy displayBrightnessStrategy = mInvalidBrightnessStrategy;
if (targetDisplayState == Display.STATE_OFF) {
displayBrightnessStrategy = mScreenOffBrightnessStrategy;
+ } else if (displayPowerRequest.boostScreenBrightness) {
+ displayBrightnessStrategy = mBoostBrightnessStrategy;
} else if (shouldUseDozeBrightnessStrategy(displayPowerRequest)) {
displayBrightnessStrategy = mDozeBrightnessStrategy;
} else if (BrightnessUtils
@@ -170,6 +176,10 @@
return new TemporaryBrightnessStrategy();
}
+ BoostBrightnessStrategy getBoostBrightnessStrategy() {
+ return new BoostBrightnessStrategy();
+ }
+
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return new InvalidBrightnessStrategy();
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
new file mode 100644
index 0000000..475ef50
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+import com.android.server.display.brightness.BrightnessUtils;
+
+/**
+ * Manages the brightness of the display when the system brightness boost is requested.
+ */
+public class BoostBrightnessStrategy implements DisplayBrightnessStrategy {
+
+ public BoostBrightnessStrategy() {
+ }
+
+ // Set the brightness to the maximum value when display brightness boost is requested
+ @Override
+ public DisplayBrightnessState updateBrightness(
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
+ // Todo(brup): Introduce a validator class and add validations before setting the brightness
+ DisplayBrightnessState displayBrightnessState =
+ BrightnessUtils.constructDisplayBrightnessState(BrightnessReason.REASON_BOOST,
+ PowerManager.BRIGHTNESS_MAX,
+ PowerManager.BRIGHTNESS_MAX);
+ return displayBrightnessState;
+ }
+
+ @Override
+ public String getName() {
+ return "BoostBrightnessStrategy";
+ }
+}
diff --git a/core/java/android/window/BackEvent.aidl b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java
similarity index 64%
copy from core/java/android/window/BackEvent.aidl
copy to services/core/java/com/android/server/display/layout/DisplayIdProducer.java
index 821f1fa..3029757 100644
--- a/core/java/android/window/BackEvent.aidl
+++ b/services/core/java/com/android/server/display/layout/DisplayIdProducer.java
@@ -14,9 +14,17 @@
* limitations under the License.
*/
-package android.window;
+package com.android.server.display.layout;
/**
- * @hide
+ * Interface for producing logical display ids.
*/
-parcelable BackEvent;
+public interface DisplayIdProducer {
+
+ /**
+ * Generates a new display ID
+ * @param isDefault if requested display is the default display.
+ * @return the next unique logical display Id.
+ */
+ int getId(boolean isDefault);
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 7e16ea8..4a466fd 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -50,15 +50,33 @@
return mDisplays.toString();
}
+ @Override
+ public boolean equals(Object obj) {
+
+ if (!(obj instanceof Layout)) {
+ return false;
+ }
+
+ Layout otherLayout = (Layout) obj;
+ return this.mDisplays.equals(otherLayout.mDisplays);
+ }
+
+ @Override
+ public int hashCode() {
+ return mDisplays.hashCode();
+ }
+
/**
* Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice.
*
* @param address Address of the device.
* @param isDefault Indicates if the device is meant to be the default display.
+ * @param isEnabled Indicates if this display is usable and can be switched on
* @return The new layout.
*/
public Display createDisplayLocked(
- @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) {
+ @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled,
+ DisplayIdProducer idProducer) {
if (contains(address)) {
Slog.w(TAG, "Attempting to add second definition for display-device: " + address);
return null;
@@ -74,7 +92,7 @@
// Note that the logical display ID is saved into the layout, so when switching between
// different layouts, a logical display can be destroyed and later recreated with the
// same logical display ID.
- final int logicalDisplayId = assignDisplayIdLocked(isDefault);
+ final int logicalDisplayId = idProducer.getId(isDefault);
final Display display = new Display(address, logicalDisplayId, isEnabled);
mDisplays.add(display);
@@ -158,25 +176,64 @@
* Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s.
*/
public static class Display {
+ public static final int POSITION_UNKNOWN = -1;
+ public static final int POSITION_FRONT = 0;
+ public static final int POSITION_REAR = 1;
+
// Address of the display device to map to this display.
private final DisplayAddress mAddress;
// Logical Display ID to apply to this display.
private final int mLogicalDisplayId;
- // Indicates that this display is not usable and should remain off.
+ // Indicates if this display is usable and can be switched on
private final boolean mIsEnabled;
+ // The direction the display faces
+ // {@link DeviceStateToLayoutMap.POSITION_FRONT} or
+ // {@link DeviceStateToLayoutMap.POSITION_REAR}.
+ // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified.
+ private int mPosition;
+
Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) {
mAddress = address;
mLogicalDisplayId = logicalDisplayId;
mIsEnabled = isEnabled;
+ mPosition = POSITION_UNKNOWN;
}
@Override
public String toString() {
- return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId
- + "(" + (mIsEnabled ? "ON" : "OFF") + ")}";
+ return "{"
+ + "dispId: " + mLogicalDisplayId
+ + "(" + (mIsEnabled ? "ON" : "OFF") + ")"
+ + ", addr: " + mAddress
+ + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition)
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Display)) {
+ return false;
+ }
+
+ Display otherDisplay = (Display) obj;
+
+ return otherDisplay.mIsEnabled == this.mIsEnabled
+ && otherDisplay.mPosition == this.mPosition
+ && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId
+ && this.mAddress.equals(otherDisplay.mAddress);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + Boolean.hashCode(mIsEnabled);
+ result = 31 * result + mPosition;
+ result = 31 * result + mLogicalDisplayId;
+ result = 31 * result + mAddress.hashCode();
+ return result;
}
public DisplayAddress getAddress() {
@@ -190,5 +247,9 @@
public boolean isEnabled() {
return mIsEnabled;
}
+
+ public void setPosition(int position) {
+ mPosition = position;
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 4855be6..ccb2633 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -15,6 +15,8 @@
*/
package com.android.server.hdmi;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
/**
@@ -33,6 +35,10 @@
super(source);
}
+ ArcTerminationActionFromAvr(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+ super(source, callback);
+ }
+
@Override
boolean start() {
mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE;
@@ -47,10 +53,19 @@
return false;
}
switch (cmd.getOpcode()) {
+ case Constants.MESSAGE_FEATURE_ABORT:
+ int originalOpcode = cmd.getParams()[0] & 0xFF;
+ if (originalOpcode == Constants.MESSAGE_TERMINATE_ARC) {
+ mState = STATE_ARC_TERMINATED;
+ audioSystem().processArcTermination();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+ return true;
+ }
+ return false;
case Constants.MESSAGE_REPORT_ARC_TERMINATED:
mState = STATE_ARC_TERMINATED;
audioSystem().processArcTermination();
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
return true;
}
return false;
@@ -79,7 +94,7 @@
audioSystem().setArcStatus(false);
}
HdmiLogger.debug("Terminate ARC was not successfully sent.");
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
});
}
@@ -88,6 +103,6 @@
// Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
audioSystem().setArcStatus(false);
HdmiLogger.debug("handleTerminateArcTimeout");
- finish();
+ finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index ccaa9255d..a026c4b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -458,8 +458,16 @@
HdmiLogger.debug("ARC is not established between TV and AVR device");
return Constants.ABORT_NOT_IN_CORRECT_MODE;
} else {
- removeAction(ArcTerminationActionFromAvr.class);
- addAndStartAction(new ArcTerminationActionFromAvr(this));
+ if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
+ && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
+ IHdmiControlCallback callback =
+ getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
+ removeAction(ArcTerminationActionFromAvr.class);
+ addAndStartAction(new ArcTerminationActionFromAvr(this, callback));
+ } else {
+ removeAction(ArcTerminationActionFromAvr.class);
+ addAndStartAction(new ArcTerminationActionFromAvr(this));
+ }
return Constants.HANDLED;
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 43cd71a..2f15e57 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -878,16 +878,30 @@
Slog.w(TAG, "Device type doesn't support ARC.");
return;
}
+ boolean isArcEnabled = false;
if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
- if (audioSystem.isArcEnabled()) {
- audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem));
- }
+ isArcEnabled = audioSystem.isArcEnabled();
if (isSystemAudioActivated()) {
audioSystem.terminateSystemAudioMode();
}
+ if (isArcEnabled) {
+ if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) {
+ audioSystem.removeAction(ArcTerminationActionFromAvr.class);
+ }
+ audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem,
+ new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ mAddressAllocated = false;
+ initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+ }
+ }));
+ }
}
- mAddressAllocated = false;
- initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+ if (!isArcEnabled) {
+ mAddressAllocated = false;
+ initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+ }
}
/**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 199519c..81d782e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -98,6 +98,7 @@
import android.view.SurfaceControl;
import android.view.VerifiedInputEvent;
import android.view.ViewConfiguration;
+import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.R;
@@ -1184,6 +1185,33 @@
keyboardLayoutDescriptor);
}
+ @Override // Binder call
+ public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
+ imeInfo, imeSubtype);
+ }
+
+ @EnforcePermission(Manifest.permission.SET_KEYBOARD_LAYOUT)
+ @Override // Binder call
+ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+ super.setKeyboardLayoutForInputDevice_enforcePermission();
+ mKeyboardLayoutManager.setKeyboardLayoutForInputDevice(identifier, userId, imeInfo,
+ imeSubtype, keyboardLayoutDescriptor);
+ }
+
+ @Override // Binder call
+ public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ return mKeyboardLayoutManager.getKeyboardLayoutListForInputDevice(identifier, userId,
+ imeInfo, imeSubtype);
+ }
+
+
public void switchKeyboardLayout(int deviceId, int direction) {
mKeyboardLayoutManager.switchKeyboardLayout(deviceId, direction);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index c2157a6..1bb14aa 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -17,6 +17,7 @@
package com.android.server.input;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -46,6 +47,8 @@
import android.util.Log;
import android.util.Slog;
import android.view.InputDevice;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;
import com.android.internal.R;
@@ -142,7 +145,7 @@
@Override
public void onInputDeviceChanged(int deviceId) {
final InputDevice inputDevice = getInputDevice(deviceId);
- if (inputDevice == null) {
+ if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
return;
}
synchronized (mDataStore) {
@@ -545,6 +548,35 @@
}
}
+ public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ // TODO(b/259530132): Implement the new keyboard layout API: Returning non-IME specific
+ // layout for now.
+ return getCurrentKeyboardLayoutForInputDevice(identifier);
+ }
+
+ public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype, String keyboardLayoutDescriptor) {
+ // TODO(b/259530132): Implement the new keyboard layout API: setting non-IME specific
+ // layout for now.
+ setCurrentKeyboardLayoutForInputDevice(identifier, keyboardLayoutDescriptor);
+ }
+
+ public String[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
+ @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
+ @NonNull InputMethodSubtype imeSubtype) {
+ // TODO(b/259530132): Implement the new keyboard layout API: Returning list of all
+ // layouts for now.
+ KeyboardLayout[] allLayouts = getKeyboardLayouts();
+ String[] allLayoutDesc = new String[allLayouts.length];
+ for (int i = 0; i < allLayouts.length; i++) {
+ allLayoutDesc[i] = allLayouts[i].getDescriptor();
+ }
+ return allLayoutDesc;
+ }
+
public void switchKeyboardLayout(int deviceId, int direction) {
mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 39b9f1f..783a6ae 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -38,6 +38,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -59,6 +60,10 @@
*/
public class LocaleManagerService extends SystemService {
private static final String TAG = "LocaleManagerService";
+ // The feature flag control that allows the active IME to query the locales of the foreground
+ // app.
+ private static final String PROP_ALLOW_IME_QUERY_APP_LOCALE =
+ "i18n.feature.allow_ime_query_app_locale";
final Context mContext;
private final LocaleManagerService.LocaleManagerBinderService mBinderService;
private ActivityTaskManagerInternal mActivityTaskManagerInternal;
@@ -431,6 +436,10 @@
* Checks if the calling app is the current input method.
*/
private boolean isCallerFromCurrentInputMethod(int userId) {
+ if (!SystemProperties.getBoolean(PROP_ALLOW_IME_QUERY_APP_LOCALE, true)) {
+ return false;
+ }
+
String currentInputMethod = Settings.Secure.getStringForUser(
mContext.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 25e71e8..0ae3a02 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -550,16 +550,6 @@
return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
}
- public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue) {
- return Settings.Global.getInt(contentResolver, keyName, defaultValue);
- }
-
- public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue, int userId) {
- return Settings.Secure.getIntForUser(contentResolver, keyName, defaultValue, userId);
- }
-
public java.security.KeyStore getJavaKeyStore() {
try {
java.security.KeyStore ks = java.security.KeyStore.getInstance(
@@ -1027,9 +1017,9 @@
private void enforceFrpResolved() {
final ContentResolver cr = mContext.getContentResolver();
- final boolean inSetupWizard = mInjector.settingsSecureGetInt(cr,
+ final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_SYSTEM) == 0;
- final boolean secureFrp = mInjector.settingsSecureGetInt(cr,
+ final boolean secureFrp = Settings.Secure.getIntForUser(cr,
Settings.Secure.SECURE_FRP_MODE, 0, UserHandle.USER_SYSTEM) == 1;
if (inSetupWizard && secureFrp) {
throw new SecurityException("Cannot change credential in SUW while factory reset"
@@ -2155,7 +2145,7 @@
if (credential == null || credential.isNone()) {
throw new IllegalArgumentException("Credential can't be null or empty");
}
- if (userId == USER_FRP && mInjector.settingsGlobalGetInt(mContext.getContentResolver(),
+ if (userId == USER_FRP && Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
@@ -3282,6 +3272,7 @@
for (UserInfo user : users) {
if (userOwnsFrpCredential(mContext, user)) {
if (!isUserSecure(user.id)) {
+ Slogf.d(TAG, "Clearing FRP credential tied to user %d", user.id);
mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
0, null);
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 807ba3c..473c4b6 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -550,7 +550,7 @@
mCache.clear();
}
- @Nullable @VisibleForTesting
+ @Nullable
PersistentDataBlockManagerInternal getPersistentDataBlockManager() {
if (mPersistentDataBlockManagerInternal == null) {
mPersistentDataBlockManagerInternal =
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 73a16fd..acd7cc1 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -32,6 +32,7 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.Settings;
import android.security.GateKeeper;
import android.security.Scrypt;
import android.service.gatekeeper.GateKeeperResponse;
@@ -457,6 +458,11 @@
mPasswordSlotManager = passwordSlotManager;
}
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+
@VisibleForTesting
protected IWeaver getWeaverService() throws RemoteException {
try {
@@ -770,6 +776,17 @@
private int getNextAvailableWeaverSlot() {
Set<Integer> usedSlots = getUsedWeaverSlots();
usedSlots.addAll(mPasswordSlotManager.getUsedSlots());
+ // If the device is not yet provisioned, then the Weaver slot used by the FRP credential may
+ // be still needed and must not be reused yet. (This *should* instead check "has FRP been
+ // resolved yet?", which would allow reusing the slot a bit earlier. However, the
+ // SECURE_FRP_MODE setting gets set to 1 too late for it to be used here.)
+ if (!isDeviceProvisioned()) {
+ PersistentData persistentData = mStorage.readPersistentDataBlock();
+ if (persistentData != null && persistentData.type == PersistentData.TYPE_SP_WEAVER) {
+ int slot = persistentData.userId; // Note: field name is misleading
+ usedSlots.add(slot);
+ }
+ }
for (int i = 0; i < mWeaverConfig.slots; i++) {
if (!usedSlots.contains(i)) {
return i;
@@ -814,9 +831,14 @@
protectorSecret = transformUnderWeaverSecret(stretchedLskf, weaverSecret);
} else {
- // Weaver is unavailable, so make the protector use Gatekeeper to verify the LSKF
- // instead. However, skip Gatekeeper when the LSKF is empty, since it wouldn't give any
- // benefit in that case as Gatekeeper isn't expected to provide secure deletion.
+ // Weaver is unavailable, so make the protector use Gatekeeper (GK) to verify the LSKF.
+ //
+ // However, skip GK when the LSKF is empty. There are two reasons for this, one
+ // performance and one correctness. The performance reason is that GK wouldn't give any
+ // benefit with an empty LSKF anyway, since GK isn't expected to provide secure
+ // deletion. The correctness reason is that it is unsafe to enroll a password in the
+ // 'fakeUserId' GK range on an FRP-protected device that is in the setup wizard with FRP
+ // not passed yet, as that may overwrite the enrollment used by the FRP credential.
if (!credential.isNone()) {
// In case GK enrollment leaves persistent state around (in RPMB), this will nuke
// them to prevent them from accumulating and causing problems.
@@ -908,12 +930,40 @@
}
}
+ private static boolean isNoneCredential(PasswordData pwd) {
+ return pwd == null || pwd.credentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE;
+ }
+
+ private boolean shouldSynchronizeFrpCredential(@Nullable PasswordData pwd, int userId) {
+ if (mStorage.getPersistentDataBlockManager() == null) {
+ return false;
+ }
+ UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (!LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
+ return false;
+ }
+ // When initializing the synthetic password of the user that will own the FRP credential,
+ // the FRP data block must not be cleared if the device isn't provisioned yet, since in this
+ // case the old value of the block may still be needed for the FRP authentication step. The
+ // FRP data block will instead be cleared later, by
+ // LockSettingsService.DeviceProvisionedObserver.clearFrpCredentialIfOwnerNotSecure().
+ //
+ // Don't check the SECURE_FRP_MODE setting here, as it gets set to 1 too late.
+ //
+ // Don't delay anything for a nonempty credential. A nonempty credential can be set before
+ // the device has been provisioned, but it's guaranteed to be after FRP was resolved.
+ if (isNoneCredential(pwd) && !isDeviceProvisioned()) {
+ Slog.d(TAG, "Not clearing FRP credential yet because device is not yet provisioned");
+ return false;
+ }
+ return true;
+ }
+
private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
int userId) {
- if (mStorage.getPersistentDataBlockManager() != null
- && LockPatternUtils.userOwnsFrpCredential(mContext,
- mUserManager.getUserInfo(userId))) {
- if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (shouldSynchronizeFrpCredential(pwd, userId)) {
+ Slogf.d(TAG, "Syncing Gatekeeper-based FRP credential tied to user %d", userId);
+ if (!isNoneCredential(pwd)) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
pwd.toBytes());
} else {
@@ -924,10 +974,9 @@
private void synchronizeWeaverFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
int userId, int weaverSlot) {
- if (mStorage.getPersistentDataBlockManager() != null
- && LockPatternUtils.userOwnsFrpCredential(mContext,
- mUserManager.getUserInfo(userId))) {
- if (pwd != null && pwd.credentialType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
+ if (shouldSynchronizeFrpCredential(pwd, userId)) {
+ Slogf.d(TAG, "Syncing Weaver-based FRP credential tied to user %d", userId);
+ if (!isNoneCredential(pwd)) {
mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
requestedQuality, pwd.toBytes());
} else {
@@ -1058,7 +1107,7 @@
AuthenticationResult result = new AuthenticationResult();
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- // This should never happen, due to the migration done in LSS.bootCompleted().
+ // This should never happen, due to the migration done in LSS.onThirdPartyAppsStarted().
Slogf.wtf(TAG, "Synthetic password not found for user %d", userId);
result.gkResponse = VerifyCredentialResponse.ERROR;
return result;
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 4bbd40d..5f8572b 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -59,6 +59,9 @@
static final int CONCURRENT_SNOOZE_LIMIT = 500;
+ // A safe size for strings to be put in persistent storage, to avoid breaking the XML write.
+ static final int MAX_STRING_LENGTH = 1000;
+
protected static final String XML_TAG_NAME = "snoozed-notifications";
private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -200,7 +203,7 @@
scheduleRepost(key, duration);
Long activateAt = System.currentTimeMillis() + duration;
synchronized (mLock) {
- mPersistedSnoozedNotifications.put(key, activateAt);
+ mPersistedSnoozedNotifications.put(getTrimmedString(key), activateAt);
}
}
@@ -210,7 +213,10 @@
protected void snooze(NotificationRecord record, String contextId) {
if (contextId != null) {
synchronized (mLock) {
- mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
+ mPersistedSnoozedNotificationsWithContext.put(
+ getTrimmedString(record.getKey()),
+ getTrimmedString(contextId)
+ );
}
}
snooze(record);
@@ -225,6 +231,13 @@
}
}
+ private String getTrimmedString(String key) {
+ if (key != null && key.length() > MAX_STRING_LENGTH) {
+ return key.substring(0, MAX_STRING_LENGTH);
+ }
+ return key;
+ }
+
protected boolean cancel(int userId, String pkg, String tag, int id) {
synchronized (mLock) {
final Set<Map.Entry<String, NotificationRecord>> records =
@@ -293,10 +306,12 @@
}
protected void repost(String key, int userId, boolean muteOnReturn) {
+ final String trimmedKey = getTrimmedString(key);
+
NotificationRecord record;
synchronized (mLock) {
- mPersistedSnoozedNotifications.remove(key);
- mPersistedSnoozedNotificationsWithContext.remove(key);
+ mPersistedSnoozedNotifications.remove(trimmedKey);
+ mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
record = mSnoozedNotifications.remove(key);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 3421eb7..79f2b3f 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -379,6 +379,8 @@
final String packageName = data.getSchemeSpecificPart();
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+ final boolean systemUpdateUninstall =
+ intent.getBooleanExtra(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, false);
final int[] userIds;
final int extraUid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
@@ -405,7 +407,7 @@
break;
case ACTION_PACKAGE_REMOVED:
if (replacing) {
- onPackageReplacing(packageName, userIds);
+ onPackageReplacing(packageName, systemUpdateUninstall, userIds);
} else {
onPackageRemoved(packageName, userIds);
}
@@ -463,7 +465,7 @@
}
private void onPackageReplacing(@NonNull final String packageName,
- @NonNull final int[] userIds) {
+ boolean systemUpdateUninstall, @NonNull final int[] userIds) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
for (int userId : userIds) {
@@ -472,8 +474,8 @@
packageName, userId);
if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
try {
- updateTargetPackagesLocked(
- mImpl.onPackageReplacing(packageName, userId));
+ updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
+ systemUpdateUninstall, userId));
} catch (OperationFailedException e) {
Slog.e(TAG, "onPackageReplacing internal error", e);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 6ffe60d..9d5830c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -21,6 +21,7 @@
import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
import static android.content.om.OverlayInfo.STATE_OVERLAY_IS_BEING_REPLACED;
+import static android.content.om.OverlayInfo.STATE_SYSTEM_UPDATE_UNINSTALL;
import static android.content.om.OverlayInfo.STATE_TARGET_IS_BEING_REPLACED;
import static android.os.UserHandle.USER_SYSTEM;
@@ -78,6 +79,7 @@
// Flags to use in conjunction with updateState.
private static final int FLAG_OVERLAY_IS_BEING_REPLACED = 1 << 1;
+ private static final int FLAG_SYSTEM_UPDATE_UNINSTALL = 1 << 2;
private final PackageManagerHelper mPackageManager;
private final IdmapManager mIdmapManager;
@@ -275,9 +277,13 @@
}
@NonNull
- Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, final int userId)
- throws OperationFailedException {
- return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
+ Set<UserPackage> onPackageReplacing(@NonNull final String pkgName,
+ boolean systemUpdateUninstall, final int userId) throws OperationFailedException {
+ int flags = FLAG_OVERLAY_IS_BEING_REPLACED;
+ if (systemUpdateUninstall) {
+ flags |= FLAG_SYSTEM_UPDATE_UNINSTALL;
+ }
+ return reconcileSettingsForPackage(pkgName, userId, flags);
}
@NonNull
@@ -840,6 +846,10 @@
return STATE_OVERLAY_IS_BEING_REPLACED;
}
+ if ((flags & FLAG_SYSTEM_UPDATE_UNINSTALL) != 0) {
+ return STATE_SYSTEM_UPDATE_UNINSTALL;
+ }
+
if (targetPackage == null) {
return STATE_MISSING_TARGET;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 21c2f2c..fad61b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -167,14 +167,16 @@
PackageState getPackageState(@NonNull String packageName);
/**
- * Iterates on all states. This should only be used when either the target package name
- * is not known or the large majority of the states are expected to be used.
- *
+ * Returns a map of all {@link PackageState PackageStates} on the device.
+ * <p>
* This will cause app visibility filtering to be invoked on each state on the device,
- * which can be expensive.
+ * which can be expensive. Prefer {@link #getPackageState(String)} if possible.
*
- * @param consumer Block to accept each state as it becomes available post-filtering.
+ * @return Mapping of package name to {@link PackageState}.
*/
+ @NonNull
+ Map<String, PackageState> getPackageStates();
+
void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 9e1bffb..cf59a1e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7317,6 +7317,7 @@
}
consumer.accept(mPackageStateMutator);
+ mPackageStateMutator.onFinished();
onChanged();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 77334e5..a72ae56 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -142,6 +142,11 @@
public static final Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
pkgSetting -> pkgSetting.getPkg() == null;
+ // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch.
+ // A proper fix should be implemented in master instead.
+ public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS =
+ ThreadLocal.withInitial(() -> false);
+
/**
* Components of apps targeting Android T and above will stop receiving intents from
* external callers that do not match its declared intent filters.
@@ -1093,6 +1098,8 @@
PlatformCompat compat, ComponentResolverApi resolver,
List<ResolveInfo> resolveInfos, boolean isReceiver,
Intent intent, String resolvedType, int filterCallingUid) {
+ if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
final Printer logPrinter = DEBUG_INTENT_MATCHING
? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
: null;
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 4cac115..dd580a5 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -115,6 +115,7 @@
final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid;
extras.putInt(Intent.EXTRA_UID, removedUid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, mDataRemoved);
+ extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, mIsRemovedPackageSystemUpdate);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
extras.putBoolean(Intent.EXTRA_USER_INITIATED, !removedBySystem);
final boolean isReplace = mIsUpdate || mIsRemovedPackageSystemUpdate;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 3ec6e7d..b18179e 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -664,7 +664,9 @@
mUserStates.put(other.mUserStates.keyAt(i),
other.mUserStates.valueAt(i).snapshot());
} else {
- mUserStates.put(other.mUserStates.keyAt(i), other.mUserStates.valueAt(i));
+ var userState = other.mUserStates.valueAt(i);
+ userState.setWatchable(this);
+ mUserStates.put(other.mUserStates.keyAt(i), userState);
}
}
@@ -1378,7 +1380,11 @@
return mSecondaryCpuAbi;
}
-
+ @ApplicationInfo.HiddenApiEnforcementPolicy
+ @Override
+ public int getHiddenApiEnforcementPolicy() {
+ return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
+ }
// Code below generated by codegen v1.0.23.
//
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 4aba016..bc9f7b2 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -367,8 +367,12 @@
@Watched(manual = true)
private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
+ // Current settings file.
private final File mSettingsFilename;
- private final File mBackupSettingsFilename;
+ // Previous settings file.
+ // Removed when the current settings file successfully stored.
+ private final File mPreviousSettingsFilename;
+
private final File mPackageListFilename;
private final File mStoppedPackagesFilename;
private final File mBackupStoppedPackagesFilename;
@@ -635,7 +639,7 @@
mRuntimePermissionsPersistence = null;
mPermissionDataProvider = null;
mSettingsFilename = null;
- mBackupSettingsFilename = null;
+ mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
@@ -706,7 +710,7 @@
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
mSettingsFilename = new File(mSystemDir, "packages.xml");
- mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
+ mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -747,7 +751,7 @@
mLock = null;
mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
mSettingsFilename = null;
- mBackupSettingsFilename = null;
+ mPreviousSettingsFilename = null;
mPackageListFilename = null;
mStoppedPackagesFilename = null;
mBackupStoppedPackagesFilename = null;
@@ -2572,10 +2576,10 @@
// to persist settings earlier. So preserve the older
// backup for future reference since the current settings
// might have been corrupted.
- if (!mBackupSettingsFilename.exists()) {
- if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
+ if (!mPreviousSettingsFilename.exists()) {
+ if (!mSettingsFilename.renameTo(mPreviousSettingsFilename)) {
Slog.wtf(PackageManagerService.TAG,
- "Unable to backup package manager settings, "
+ "Unable to store older package manager settings, "
+ " current changes will be lost at reboot");
return;
}
@@ -2669,9 +2673,9 @@
FileUtils.sync(fstr);
fstr.close();
- // New settings successfully written, old ones are no longer
- // needed.
- mBackupSettingsFilename.delete();
+ // New settings successfully written, old ones are no longer needed.
+ mPreviousSettingsFilename.delete();
+
FileUtils.setPermissions(mSettingsFilename.toString(),
FileUtils.S_IRUSR|FileUtils.S_IWUSR
|FileUtils.S_IRGRP|FileUtils.S_IWGRP,
@@ -3109,16 +3113,15 @@
boolean readLPw(@NonNull Computer computer, @NonNull List<UserInfo> users) {
FileInputStream str = null;
- if (mBackupSettingsFilename.exists()) {
+ if (mPreviousSettingsFilename.exists()) {
try {
- str = new FileInputStream(mBackupSettingsFilename);
+ str = new FileInputStream(mPreviousSettingsFilename);
mReadMessages.append("Reading from backup settings file\n");
PackageManagerService.reportSettingsProblem(Log.INFO,
"Need to read from backup settings file");
if (mSettingsFilename.exists()) {
- // If both the backup and settings file exist, we
- // ignore the settings since it might have been
- // corrupted.
+ // If both the previous and current settings files exist,
+ // we ignore the current since it might have been corrupted.
Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
+ mSettingsFilename);
mSettingsFilename.delete();
@@ -5594,8 +5597,8 @@
}
private static final class RuntimePermissionPersistence {
- // 200-400ms delay to avoid monopolizing PMS lock when written for multiple users.
- private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 300;
+ // 700-1300ms delay to avoid monopolizing PMS lock when written for multiple users.
+ private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 1000;
private static final double WRITE_PERMISSIONS_DELAY_JITTER = 0.3;
private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
@@ -5613,8 +5616,7 @@
// Low-priority handlers running on SystemBg thread.
private final Handler mAsyncHandler = new MyHandler();
- private final Handler mPersistenceHandler = new Handler(
- BackgroundThread.getHandler().getLooper());
+ private final Handler mPersistenceHandler = new PersistenceHandler();
private final Object mLock = new Object();
@@ -5761,20 +5763,22 @@
@NonNull WatchedArrayMap<String, SharedUserSetting> sharedUsers,
@Nullable Handler pmHandler, @NonNull Object pmLock,
boolean sync) {
- final int version;
- final String fingerprint;
- final boolean isLegacyPermissionStateStale;
synchronized (mLock) {
mAsyncHandler.removeMessages(userId);
mWriteScheduled.delete(userId);
-
- version = mVersions.get(userId, INITIAL_VERSION);
- fingerprint = mFingerprints.get(userId);
- isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
- mIsLegacyPermissionStateStale = false;
}
Runnable writer = () -> {
+ final int version;
+ final String fingerprint;
+ final boolean isLegacyPermissionStateStale;
+ synchronized (mLock) {
+ version = mVersions.get(userId, INITIAL_VERSION);
+ fingerprint = mFingerprints.get(userId);
+ isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
+ mIsLegacyPermissionStateStale = false;
+ }
+
final RuntimePermissionsState runtimePermissions;
synchronized (pmLock) {
if (sync || isLegacyPermissionStateStale) {
@@ -5823,7 +5827,7 @@
}
if (pmHandler != null) {
// Async version.
- mPersistenceHandler.post(() -> writePendingStates());
+ mPersistenceHandler.obtainMessage(userId).sendToTarget();
} else {
// Sync version.
writePendingStates();
@@ -6099,6 +6103,17 @@
}
}
}
+
+ private final class PersistenceHandler extends Handler {
+ PersistenceHandler() {
+ super(BackgroundThread.getHandler().getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ writePendingStates();
+ }
+ }
}
/**
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 f388e07..9f21097 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -298,7 +298,8 @@
dexMetadataType,
apkType,
ISA_MAP.getOrDefault(isa,
- ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN));
+ ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN),
+ ArtStatsLog.ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_UNKNOWN);
}
}
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 4ff0d59..024b63e 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -22,6 +22,7 @@
import android.annotation.UserIdInt;
import android.os.Binder;
import android.os.UserHandle;
+import android.util.ArrayMap;
import com.android.server.pm.Computer;
import com.android.server.pm.PackageManagerLocal;
@@ -30,7 +31,6 @@
import com.android.server.pm.snapshot.PackageDataSnapshot;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -143,7 +143,7 @@
private final int mUserId;
@Nullable
- private ArrayList<PackageState> mFilteredPackageStates;
+ private Map<String, PackageState> mFilteredPackageStates;
@Nullable
private final UnfilteredSnapshotImpl mParentSnapshot;
@@ -179,25 +179,36 @@
return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
}
+ @NonNull
@Override
- public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+ public Map<String, PackageState> getPackageStates() {
checkClosed();
if (mFilteredPackageStates == null) {
var packageStates = mSnapshot.getPackageStates();
- var filteredPackageStates = new ArrayList<PackageState>();
+ var filteredPackageStates = new ArrayMap<String, PackageState>();
for (int index = 0, size = packageStates.size(); index < size; index++) {
var packageState = packageStates.valueAt(index);
if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
- filteredPackageStates.add(packageState);
+ filteredPackageStates.put(packageStates.keyAt(index), packageState);
}
}
- mFilteredPackageStates = filteredPackageStates;
+ mFilteredPackageStates = Collections.unmodifiableMap(filteredPackageStates);
}
- for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
- var packageState = mFilteredPackageStates.get(index);
- consumer.accept(packageState);
+ return mFilteredPackageStates;
+ }
+
+ @Override
+ public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+ checkClosed();
+
+ var packageStates = mSnapshot.getPackageStates();
+ for (int index = 0, size = packageStates.size(); index < size; index++) {
+ var packageState = packageStates.valueAt(index);
+ if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
+ consumer.accept(packageState);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index c76b129..82b5fa2 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -234,10 +234,12 @@
|| !pkg.getLibraryNames().isEmpty();
}
- public static int getHiddenApiEnforcementPolicy(AndroidPackage pkg,
+ public static int getHiddenApiEnforcementPolicy(@Nullable AndroidPackage pkg,
@NonNull PackageStateInternal pkgSetting) {
boolean isAllowedToUseHiddenApis;
- if (pkg.isSignedWithPlatformKey()) {
+ if (pkg == null) {
+ isAllowedToUseHiddenApis = false;
+ } else if (pkg.isSignedWithPlatformKey()) {
isAllowedToUseHiddenApis = true;
} else if (pkg.isSystem() || pkgSetting.getTransientState().isUpdatedSystemApp()) {
isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
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 9ec63fc..cefe9cd 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -467,7 +467,7 @@
@Override
public PermissionInfo getPermissionInfo(String permissionName, String packageName, int flags) {
- return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, packageName, flags);
+ return mPermissionManagerServiceImpl.getPermissionInfo(permissionName, flags, packageName);
}
@Override
@@ -792,14 +792,14 @@
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ public List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection) {
return mPermissionManagerServiceImpl.getAllPermissionsWithProtection(protection);
}
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ public List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags) {
return mPermissionManagerServiceImpl
.getAllPermissionsWithProtectionFlags(protectionFlags);
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 5ffbbdc..e56edeb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -559,8 +559,8 @@
@Override
@Nullable
- public PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
- @PackageManager.PermissionInfoFlags int flags) {
+ public PermissionInfo getPermissionInfo(@NonNull String permName,
+ @PackageManager.PermissionInfoFlags int flags, @NonNull String opPackageName) {
final int callingUid = Binder.getCallingUid();
if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
return null;
@@ -2127,7 +2127,7 @@
for (int i = 0; i < numRequestedPermissions; i++) {
PermissionInfo permInfo = getPermissionInfo(
newPackage.getRequestedPermissions().get(i),
- newPackage.getPackageName(), 0);
+ 0, newPackage.getPackageName());
if (permInfo == null) {
continue;
}
@@ -5204,9 +5204,9 @@
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ public List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection) {
- ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+ List<PermissionInfo> matchingPermissions = new ArrayList<>();
synchronized (mLock) {
for (final Permission permission : mRegistry.getPermissions()) {
@@ -5221,9 +5221,9 @@
@NonNull
@Override
- public ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ public List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags) {
- ArrayList<PermissionInfo> matchingPermissions = new ArrayList<>();
+ List<PermissionInfo> matchingPermissions = new ArrayList<>();
synchronized (mLock) {
for (final Permission permission : mRegistry.getPermissions()) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 930936b..d9caec7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -32,7 +32,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -77,8 +76,8 @@
* @return a {@link PermissionInfo} containing information about the permission, or {@code null}
* if not found
*/
- PermissionInfo getPermissionInfo(@NonNull String permName, @NonNull String opPackageName,
- @PackageManager.PermissionInfoFlags int flags);
+ PermissionInfo getPermissionInfo(@NonNull String permName,
+ @PackageManager.PermissionInfoFlags int flags, @NonNull String opPackageName);
/**
* Query for all of the permissions associated with a particular group.
@@ -487,11 +486,11 @@
/** Get all permissions that have a certain protection */
@NonNull
- ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection);
/** Get all permissions that have certain protection flags */
- @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ @NonNull List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags);
/**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index f20620e..97ac749 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -164,11 +164,12 @@
/** Get all permissions that have a certain protection */
@NonNull
- ArrayList<PermissionInfo> getAllPermissionsWithProtection(
+ List<PermissionInfo> getAllPermissionsWithProtection(
@PermissionInfo.Protection int protection);
- /** Get all permissions that have certain protection flags */
- @NonNull ArrayList<PermissionInfo> getAllPermissionsWithProtectionFlags(
+ /** Get all permissions that have certain protection flags
+ * @return*/
+ @NonNull List<PermissionInfo> getAllPermissionsWithProtectionFlags(
@PermissionInfo.ProtectionFlags int protectionFlags);
/**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e8d0640..67b7647 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -112,6 +112,19 @@
int getAppId();
/**
+ * Retrieves effective hidden API policy for this app. The state can be dependent on
+ * {@link #getAndroidPackage()} availability and whether the app is a system app.
+ *
+ * Note that during process start, this policy may be mutated by device specific process
+ * configuration, so this value isn't truly final.
+ *
+ * @return The (mostly) final {@link ApplicationInfo.HiddenApiEnforcementPolicy} that should be
+ * applied to this package.
+ */
+ @ApplicationInfo.HiddenApiEnforcementPolicy
+ int getHiddenApiEnforcementPolicy();
+
+ /**
* @see PackageInfo#packageName
* @see AndroidPackage#getPackageName()
*/
@@ -139,6 +152,18 @@
String getSeInfo();
/**
+ * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+ */
+ @NonNull
+ PackageUserState getStateForUser(@NonNull UserHandle user);
+
+ /**
+ * @see R.styleable#AndroidManifestUsesLibrary
+ */
+ @NonNull
+ List<SharedLibrary> getUsesLibraries();
+
+ /**
* @see AndroidPackage#isPrivileged()
*/
boolean isPrivileged();
@@ -154,18 +179,6 @@
*/
boolean isUpdatedSystemApp();
- /**
- * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
- */
- @NonNull
- PackageUserState getStateForUser(@NonNull UserHandle user);
-
- /**
- * @see R.styleable#AndroidManifestUsesLibrary
- */
- @NonNull
- List<SharedLibrary> getUsesLibraries();
-
// Methods below this comment are not yet exposed as API
/**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index e552a34..43d019a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SigningInfo;
@@ -118,6 +119,8 @@
private final int mCategoryOverride;
@Nullable
private final String mCpuAbiOverride;
+ @ApplicationInfo.HiddenApiEnforcementPolicy
+ private final int mHiddenApiEnforcementPolicy;
private final long mLastModifiedTime;
private final long mLastUpdateTime;
private final long mLongVersionCode;
@@ -170,6 +173,7 @@
mAppId = pkgState.getAppId();
mCategoryOverride = pkgState.getCategoryOverride();
mCpuAbiOverride = pkgState.getCpuAbiOverride();
+ mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy();
mLastModifiedTime = pkgState.getLastModifiedTime();
mLastUpdateTime = pkgState.getLastUpdateTime();
mLongVersionCode = pkgState.getVersionCode();
@@ -545,7 +549,7 @@
}
@DataClass.Generated(
- time = 1665778832625L,
+ time = 1666719622708L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -609,6 +613,11 @@
}
@DataClass.Generated.Member
+ public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
+ return mHiddenApiEnforcementPolicy;
+ }
+
+ @DataClass.Generated.Member
public long getLastModifiedTime() {
return mLastModifiedTime;
}
@@ -705,10 +714,10 @@
}
@DataClass.Generated(
- time = 1665778832668L,
+ time = 1666719622749L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index a536f90..b3deb1c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -44,7 +44,7 @@
/** @hide */
@DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
@DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock",
- "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths"})
+ "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable"})
public class PackageUserStateImpl extends WatchableImpl implements PackageUserStateInternal,
Snappable {
@@ -92,8 +92,9 @@
private long mFirstInstallTime;
+ // TODO(b/239050028): Remove, enforce notifying parent through PMS commit method
@Nullable
- private final Watchable mWatchable;
+ private Watchable mWatchable;
@NonNull
final SnapshotCache<PackageUserStateImpl> mSnapshot;
@@ -550,71 +551,30 @@
? Collections.emptyMap() : mSharedLibraryOverlayPaths;
}
- @Override
- public boolean equals(@Nullable Object o) {
- // You can override field equality logic by defining either of the methods like:
- // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
- // boolean fieldNameEquals(FieldType otherValue) { ... }
-
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- @SuppressWarnings("unchecked")
- PackageUserStateImpl that = (PackageUserStateImpl) o;
- //noinspection PointlessBooleanExpression
- return Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
- && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
- && mCeDataInode == that.mCeDataInode
- && mInstalled == that.mInstalled
- && mStopped == that.mStopped
- && mNotLaunched == that.mNotLaunched
- && mHidden == that.mHidden
- && mDistractionFlags == that.mDistractionFlags
- && mInstantApp == that.mInstantApp
- && mVirtualPreload == that.mVirtualPreload
- && mEnabledState == that.mEnabledState
- && mInstallReason == that.mInstallReason
- && mUninstallReason == that.mUninstallReason
- && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
- && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
- && Objects.equals(mOverlayPaths, that.mOverlayPaths)
- && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
- && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
- && Objects.equals(mSuspendParams, that.mSuspendParams)
- && Objects.equals(mComponentLabelIconOverrideMap,
- that.mComponentLabelIconOverrideMap)
- && mFirstInstallTime == that.mFirstInstallTime
- && Objects.equals(mWatchable, that.mWatchable);
+ @NonNull
+ public PackageUserStateImpl setWatchable(@NonNull Watchable watchable) {
+ mWatchable = watchable;
+ return this;
}
- @Override
- public int hashCode() {
- // You can override field hashCode logic by defining methods like:
- // int fieldNameHashCode() { ... }
+ private boolean watchableEquals(Watchable other) {
+ // Ignore the Watchable for equality
+ return true;
+ }
- int _hash = 1;
- _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
- _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
- _hash = 31 * _hash + Long.hashCode(mCeDataInode);
- _hash = 31 * _hash + Boolean.hashCode(mInstalled);
- _hash = 31 * _hash + Boolean.hashCode(mStopped);
- _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
- _hash = 31 * _hash + Boolean.hashCode(mHidden);
- _hash = 31 * _hash + mDistractionFlags;
- _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
- _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
- _hash = 31 * _hash + mEnabledState;
- _hash = 31 * _hash + mInstallReason;
- _hash = 31 * _hash + mUninstallReason;
- _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning);
- _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller);
- _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
- _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
- _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
- _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
- _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
- _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
- _hash = 31 * _hash + Objects.hashCode(mWatchable);
- return _hash;
+ private int watchableHashCode() {
+ // Ignore the Watchable for equality
+ return 0;
+ }
+
+ private boolean snapshotEquals(SnapshotCache<PackageUserStateImpl> other) {
+ // Ignore the SnapshotCache for equality
+ return true;
+ }
+
+ private int snapshotHashCode() {
+ // Ignore the SnapshotCache for equality
+ return 0;
}
@@ -736,11 +696,6 @@
}
@DataClass.Generated.Member
- public @Nullable Watchable getWatchable() {
- return mWatchable;
- }
-
- @DataClass.Generated.Member
public @NonNull SnapshotCache<PackageUserStateImpl> getSnapshot() {
return mSnapshot;
}
@@ -778,11 +733,82 @@
return this;
}
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(PackageUserStateImpl other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ PackageUserStateImpl that = (PackageUserStateImpl) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
+ && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
+ && mCeDataInode == that.mCeDataInode
+ && mInstalled == that.mInstalled
+ && mStopped == that.mStopped
+ && mNotLaunched == that.mNotLaunched
+ && mHidden == that.mHidden
+ && mDistractionFlags == that.mDistractionFlags
+ && mInstantApp == that.mInstantApp
+ && mVirtualPreload == that.mVirtualPreload
+ && mEnabledState == that.mEnabledState
+ && mInstallReason == that.mInstallReason
+ && mUninstallReason == that.mUninstallReason
+ && Objects.equals(mHarmfulAppWarning, that.mHarmfulAppWarning)
+ && Objects.equals(mLastDisableAppCaller, that.mLastDisableAppCaller)
+ && Objects.equals(mOverlayPaths, that.mOverlayPaths)
+ && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths)
+ && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme)
+ && Objects.equals(mSuspendParams, that.mSuspendParams)
+ && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap)
+ && mFirstInstallTime == that.mFirstInstallTime
+ && watchableEquals(that.mWatchable)
+ && snapshotEquals(that.mSnapshot);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
+ _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
+ _hash = 31 * _hash + Long.hashCode(mCeDataInode);
+ _hash = 31 * _hash + Boolean.hashCode(mInstalled);
+ _hash = 31 * _hash + Boolean.hashCode(mStopped);
+ _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
+ _hash = 31 * _hash + Boolean.hashCode(mHidden);
+ _hash = 31 * _hash + mDistractionFlags;
+ _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
+ _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
+ _hash = 31 * _hash + mEnabledState;
+ _hash = 31 * _hash + mInstallReason;
+ _hash = 31 * _hash + mUninstallReason;
+ _hash = 31 * _hash + Objects.hashCode(mHarmfulAppWarning);
+ _hash = 31 * _hash + Objects.hashCode(mLastDisableAppCaller);
+ _hash = 31 * _hash + Objects.hashCode(mOverlayPaths);
+ _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths);
+ _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme);
+ _hash = 31 * _hash + Objects.hashCode(mSuspendParams);
+ _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap);
+ _hash = 31 * _hash + Long.hashCode(mFirstInstallTime);
+ _hash = 31 * _hash + watchableHashCode();
+ _hash = 31 * _hash + snapshotHashCode();
+ return _hash;
+ }
+
@DataClass.Generated(
- time = 1645040852569L,
+ time = 1668033772891L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
- inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate final @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @java.lang.Override boolean equals(java.lang.Object)\npublic @java.lang.Override int hashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+ inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate long mFirstInstallTime\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTime(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index e736f43..4a8ef96 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -40,6 +40,8 @@
private final Function<String, PackageSetting> mActiveStateFunction;
private final Function<String, PackageSetting> mDisabledStateFunction;
+ private final ArraySet<PackageSetting> mChangedStates = new ArraySet<>();
+
public PackageStateMutator(@NonNull Function<String, PackageSetting> activeStateFunction,
@NonNull Function<String, PackageSetting> disabledStateFunction) {
mActiveStateFunction = activeStateFunction;
@@ -52,23 +54,23 @@
@NonNull
public PackageStateWrite forPackage(@NonNull String packageName) {
- return mStateWrite.setState(mActiveStateFunction.apply(packageName));
+ return setState(mActiveStateFunction.apply(packageName));
}
@Nullable
public PackageStateWrite forPackageNullable(@NonNull String packageName) {
final PackageSetting packageState = mActiveStateFunction.apply(packageName);
- mStateWrite.setState(packageState);
+ setState(packageState);
if (packageState == null) {
return null;
}
- return mStateWrite.setState(packageState);
+ return setState(packageState);
}
@NonNull
public PackageStateWrite forDisabledSystemPackage(@NonNull String packageName) {
- return mStateWrite.setState(mDisabledStateFunction.apply(packageName));
+ return setState(mDisabledStateFunction.apply(packageName));
}
@Nullable
@@ -78,7 +80,7 @@
return null;
}
- return mStateWrite.setState(packageState);
+ return setState(packageState);
}
@NonNull
@@ -109,6 +111,21 @@
}
}
+ public void onFinished() {
+ for (int index = 0; index < mChangedStates.size(); index++) {
+ mChangedStates.valueAt(index).onChanged();
+ }
+ }
+
+ @NonNull
+ private StateWriteWrapper setState(@Nullable PackageSetting state) {
+ // State can be nullable because this infrastructure no-ops on non-existent states
+ if (state != null) {
+ mChangedStates.add(state);
+ }
+ return mStateWrite.setState(state);
+ }
+
public static class InitialState {
private final int mPackageSequence;
@@ -173,8 +190,11 @@
@NonNull
@Override
public PackageUserStateWrite userState(int userId) {
- return mUserStateWrite.setStates(
- mState == null ? null : mState.getOrCreateUserState(userId));
+ var userState = mState == null ? null : mState.getOrCreateUserState(userId);
+ if (userState != null) {
+ userState.setWatchable(mState);
+ }
+ return mUserStateWrite.setStates(userState);
}
@Override
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index d2e0502..91bb677 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -378,13 +378,14 @@
try {
conditionSatisfied = mStateConditions.get(state).getAsBoolean();
} catch (IllegalStateException e) {
- // Failed to compute the current state based on current available data. Return
+ // Failed to compute the current state based on current available data. Continue
// with the expectation that notifyDeviceStateChangedIfNeeded() will be called
- // when a callback with the missing data is triggered.
+ // when a callback with the missing data is triggered. May trigger another state
+ // change if another state is satisfied currently.
if (DEBUG) {
Slog.d(TAG, "Unable to check current state", e);
}
- return;
+ continue;
}
if (conditionSatisfied) {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e61effa..d6cac33 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -232,7 +232,7 @@
}
};
- final ArrayList<PermissionInfo> dangerousPerms =
+ final List<PermissionInfo> dangerousPerms =
mPermissionManagerInternal.getAllPermissionsWithProtection(
PermissionInfo.PROTECTION_DANGEROUS);
try {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e9c93ee..2f0f88a 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -151,6 +151,7 @@
import android.provider.DeviceConfig;
import android.provider.MediaStore;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.service.dreams.DreamManagerInternal;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
@@ -516,6 +517,7 @@
int mDoublePressOnStemPrimaryBehavior;
int mTriplePressOnStemPrimaryBehavior;
int mLongPressOnStemPrimaryBehavior;
+ boolean mStylusButtonsDisabled = false;
boolean mHasSoftInput = false;
boolean mHapticTextHandleEnabled;
boolean mUseTvRouting;
@@ -771,6 +773,9 @@
resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.POWER_BUTTON_SUPPRESSION_DELAY_AFTER_GESTURE_WAKE), false, this,
UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.STYLUS_BUTTONS_DISABLED), false, this,
+ UserHandle.USER_ALL);
updateSettings();
}
@@ -2560,6 +2565,9 @@
Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_keyChordPowerVolumeUp));
+
+ mStylusButtonsDisabled = Settings.Secure.getIntForUser(resolver,
+ Secure.STYLUS_BUTTONS_DISABLED, 0, UserHandle.USER_CURRENT) == 1;
}
if (updateRotation) {
updateRotation(true);
@@ -2912,6 +2920,27 @@
return key_consumed;
}
break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.goToFullscreenFromSplit();
+ }
+ return key_consumed;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ enterStageSplitFromRunningApp(true /* leftOrTop */);
+ return key_consumed;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+ enterStageSplitFromRunningApp(false /* leftOrTop */);
+ return key_consumed;
+ }
+ break;
case KeyEvent.KEYCODE_SLASH:
if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
@@ -3566,6 +3595,13 @@
}
}
+ private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+ if (statusbar != null) {
+ statusbar.enterStageSplitFromRunningApp(leftOrTop);
+ }
+ }
+
void launchHomeFromHotKey(int displayId) {
launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
}
@@ -4183,7 +4219,9 @@
case KeyEvent.KEYCODE_DEMO_APP_3:
case KeyEvent.KEYCODE_DEMO_APP_4: {
// TODO(b/254604589): Dispatch KeyEvent to System UI.
- sendSystemKeyToStatusBarAsync(keyCode);
+ if (!mStylusButtonsDisabled) {
+ sendSystemKeyToStatusBarAsync(keyCode);
+ }
// Just drop if keys are not intercepted for direct key.
result &= ~ACTION_PASS_TO_USER;
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 0c5e451..af4fa85 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -12229,6 +12229,7 @@
@GuardedBy("this")
private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
final int infoSize = deltaInfo.getSpecificInfoLength();
+
if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
== AccessNetworkConstants.AccessNetworkType.UNKNOWN
&& deltaInfo.getSpecificInfoFrequencyRange(0)
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 806ed64..2c7aea9 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -15,45 +15,79 @@
*/
package com.android.server.power.stats;
+import android.annotation.Nullable;
import android.os.BatteryConsumer;
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
import android.util.Log;
+import android.util.LongArrayQueue;
import android.util.SparseArray;
import com.android.internal.os.PowerProfile;
+import com.android.internal.power.ModemPowerProfile;
+
+import java.util.ArrayList;
public class MobileRadioPowerCalculator extends PowerCalculator {
private static final String TAG = "MobRadioPowerCalculator";
private static final boolean DEBUG = PowerCalculator.DEBUG;
+ private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+
private static final int NUM_SIGNAL_STRENGTH_LEVELS =
CellSignalStrength.getNumSignalStrengthLevels();
private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+ private static final int IGNORE = -1;
- private final UsageBasedPowerEstimator mActivePowerEstimator;
+ private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
- new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
- private final UsageBasedPowerEstimator mScanPowerEstimator;
+ new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
+ private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
+
+ @Nullable
+ private final UsageBasedPowerEstimator mSleepPowerEstimator;
+ @Nullable
+ private final UsageBasedPowerEstimator mIdlePowerEstimator;
+
+ private final PowerProfile mPowerProfile;
private static class PowerAndDuration {
- public long durationMs;
+ public long remainingDurationMs;
public double remainingPowerMah;
public long totalAppDurationMs;
public double totalAppPowerMah;
- public long signalDurationMs;
- public long noCoverageDurationMs;
}
public MobileRadioPowerCalculator(PowerProfile profile) {
- // Power consumption when radio is active
+ mPowerProfile = profile;
+
+ final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+ Double.NaN);
+ if (Double.isNaN(sleepDrainRateMa)) {
+ mSleepPowerEstimator = null;
+ } else {
+ mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
+ }
+
+ final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+ PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+ Double.NaN);
+ if (Double.isNaN(idleDrainRateMa)) {
+ mIdlePowerEstimator = null;
+ } else {
+ mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
+ }
+
+ // Instantiate legacy power estimators
double powerRadioActiveMa =
- profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
- if (powerRadioActiveMa == -1) {
+ profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
+ if (Double.isNaN(powerRadioActiveMa)) {
double sum = 0;
sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
@@ -61,11 +95,10 @@
}
powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
}
-
mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
- // Power consumption when radio is on, but idle
- if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
+ if (!Double.isNaN(
+ profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
@@ -95,6 +128,23 @@
PowerAndDuration total = new PowerAndDuration();
+ final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+ final int powerModel = getPowerModel(totalConsumptionUC, query);
+
+ final double totalActivePowerMah;
+ final ArrayList<UidBatteryConsumer.Builder> apps;
+ final LongArrayQueue appDurationsMs;
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ // Measured energy is available, don't bother calculating power.
+ totalActivePowerMah = Double.NaN;
+ apps = null;
+ appDurationsMs = null;
+ } else {
+ totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
+ apps = new ArrayList();
+ appDurationsMs = new LongArrayQueue();
+ }
+
final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
builder.getUidBatteryConsumerBuilders();
BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
@@ -110,132 +160,352 @@
}
}
- calculateApp(app, uid, total, query, keys);
+ // Sum and populate each app's active radio duration.
+ final long radioActiveDurationMs = calculateDuration(uid,
+ BatteryStats.STATS_SINCE_CHARGED);
+ if (!app.isVirtualUid()) {
+ total.totalAppDurationMs += radioActiveDurationMs;
+ }
+ app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ radioActiveDurationMs);
+
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ // Measured energy is available, populate the consumed power now.
+ final long appConsumptionUC = uid.getMobileRadioMeasuredBatteryConsumptionUC();
+ if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
+ final double appConsumptionMah = uCtoMah(appConsumptionUC);
+ if (!app.isVirtualUid()) {
+ total.totalAppPowerMah += appConsumptionMah;
+ }
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ appConsumptionMah, powerModel);
+
+ if (query.isProcessStateDataNeeded() && keys != null) {
+ for (BatteryConsumer.Key key : keys) {
+ final int processState = key.processState;
+ if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ // Already populated with the total across all process states
+ continue;
+ }
+ final long consumptionInStateUc =
+ uid.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+ final double powerInStateMah = uCtoMah(consumptionInStateUc);
+ app.setConsumedPower(key, powerInStateMah, powerModel);
+ }
+ }
+ }
+ } else {
+ // Cache the app and its active duration for later calculations.
+ apps.add(app);
+ appDurationsMs.addLast(radioActiveDurationMs);
+ }
}
- final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
- final int powerModel = getPowerModel(totalConsumptionUC, query);
- calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, totalConsumptionUC);
+ long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ if (totalActiveDurationMs < total.totalAppDurationMs) {
+ totalActiveDurationMs = total.totalAppDurationMs;
+ }
+
+ if (powerModel != BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ // Need to smear the calculated total active power across the apps based on app
+ // active durations.
+ final int appSize = apps.size();
+ for (int i = 0; i < appSize; i++) {
+ final UidBatteryConsumer.Builder app = apps.get(i);
+ final long activeDurationMs = appDurationsMs.get(i);
+
+ // Proportionally attribute radio power consumption based on active duration.
+ final double appConsumptionMah;
+ if (totalActiveDurationMs == 0.0) {
+ appConsumptionMah = 0.0;
+ } else {
+ appConsumptionMah =
+ (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
+ }
+
+ if (!app.isVirtualUid()) {
+ total.totalAppPowerMah += appConsumptionMah;
+ }
+ app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ appConsumptionMah, powerModel);
+
+ if (query.isProcessStateDataNeeded() && keys != null) {
+ final BatteryStats.Uid uid = app.getBatteryStatsUid();
+ for (BatteryConsumer.Key key : keys) {
+ final int processState = key.processState;
+ if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ // Already populated with the total across all process states
+ continue;
+ }
+
+ final long durationInStateMs =
+ uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
+ // Proportionally attribute per process state radio power consumption
+ // based on time state duration.
+ final double powerInStateMah;
+ if (activeDurationMs == 0.0) {
+ powerInStateMah = 0.0;
+ } else {
+ powerInStateMah =
+ (appConsumptionMah * durationInStateMs) / activeDurationMs;
+ }
+ app.setConsumedPower(key, powerInStateMah, powerModel);
+ }
+ }
+ }
+ }
+
+ total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
+
+ // Calculate remaining power consumption.
+ if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+ total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
+ if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
+ } else {
+ // Smear unattributed active time and add it to the remaining power consumption.
+ total.remainingPowerMah +=
+ (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+
+ // Calculate the inactive modem power consumption.
+ final BatteryStats.ControllerActivityCounter modemActivity =
+ batteryStats.getModemControllerActivity();
+ if (modemActivity != null && (mSleepPowerEstimator != null
+ || mIdlePowerEstimator != null)) {
+ final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
+ final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
+ BatteryStats.STATS_SINCE_CHARGED);
+ total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
+ } else {
+ // Modem activity counters unavailable. Use legacy calculations for inactive usage.
+ final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+ for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+ long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+ }
+ }
+
+ }
if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
builder.getAggregateBatteryConsumerBuilder(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- total.durationMs)
+ total.remainingDurationMs + total.totalAppDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.remainingPowerMah + total.totalAppPowerMah, powerModel);
builder.getAggregateBatteryConsumerBuilder(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- total.durationMs)
+ total.totalAppDurationMs)
.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
total.totalAppPowerMah, powerModel);
}
}
- private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
- PowerAndDuration total,
- BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
- final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
- final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
- final int powerModel = getPowerModel(consumptionUC, query);
- final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
-
- if (!app.isVirtualUid()) {
- total.totalAppDurationMs += radioActiveDurationMs;
- total.totalAppPowerMah += powerMah;
- }
-
- app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
- radioActiveDurationMs)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
- powerModel);
-
- if (query.isProcessStateDataNeeded() && keys != null) {
- for (BatteryConsumer.Key key: keys) {
- final int processState = key.processState;
- if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
- // Already populated with the total across all process states
- continue;
- }
-
- final long durationInStateMs =
- u.getMobileRadioActiveTimeInProcessState(processState) / 1000;
- final long consumptionInStateUc =
- u.getMobileRadioMeasuredBatteryConsumptionUC(processState);
- final double powerInStateMah = calculatePower(u, powerModel, durationInStateMs,
- consumptionInStateUc);
- app.setConsumedPower(key, powerInStateMah, powerModel);
- }
- }
- }
-
private long calculateDuration(BatteryStats.Uid u, int statsType) {
return u.getMobileRadioActiveTime(statsType) / 1000;
}
- private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
- long radioActiveDurationMs, long measuredChargeUC) {
- if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
- return uCtoMah(measuredChargeUC);
+ private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
+ final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
+ final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
+ double consumptionMah = 0.0;
+
+ if (DEBUG) {
+ Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
+ + elapsedRealtimeMs + " ms");
}
- if (radioActiveDurationMs > 0) {
- return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+ boolean hasConstants = false;
+
+ for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+ final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+ ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+ for (int freq = 0; freq < freqCount; freq++) {
+ for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
+ final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
+ elapsedRealtimeMs);
+ if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+ continue;
+ }
+ final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
+ txDurationMs);
+ if (Double.isNaN(txConsumptionMah)) {
+ continue;
+ }
+ hasConstants = true;
+ consumptionMah += txConsumptionMah;
+ }
+
+ final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
+ elapsedRealtimeMs);
+ if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+ continue;
+ }
+ final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
+ if (Double.isNaN(rxConsumptionMah)) {
+ continue;
+ }
+ hasConstants = true;
+ consumptionMah += rxConsumptionMah;
+ }
}
- return 0;
+
+ if (!hasConstants) {
+ final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
+ BatteryStats.STATS_SINCE_CHARGED) / 1000;
+ if (DEBUG) {
+ Log.d(TAG,
+ "Failed to calculate radio power consumption. Reattempted with legacy "
+ + "method. Radio active duration : "
+ + radioActiveDurationMs + " ms");
+ }
+ if (radioActiveDurationMs > 0) {
+ consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+ } else {
+ consumptionMah = 0.0;
+ }
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
+ + " mAH.");
+ }
+
+ return consumptionMah;
}
- private void calculateRemaining(PowerAndDuration total,
- @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
- long rawRealtimeUs, long totalConsumptionUC) {
- long signalTimeMs = 0;
- double powerMah = 0;
+ private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
+ @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
+ int txLevel) {
+ long key = PowerProfile.SUBSYSTEM_MODEM;
- if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
- powerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
- if (powerMah < 0) powerMah = 0;
+ // Attach Modem drain type to the key if specified.
+ if (drainType != IGNORE) {
+ key |= drainType;
}
- for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
- long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
- + BatteryStats.formatCharge(p));
- }
- powerMah += p;
- }
- signalTimeMs += strengthTimeMs;
- if (i == 0) {
- total.noCoverageDurationMs = strengthTimeMs;
- }
+ // Attach RadioAccessTechnology to the key if specified.
+ switch (rat) {
+ case IGNORE:
+ // do nothing
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
+ key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
+ key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
+ break;
+ case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
+ key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
+ break;
+ default:
+ Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
}
- final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
- long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
- BatteryStats.STATS_SINCE_CHARGED) / 1000;
- long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
-
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- final double p = calcScanTimePowerMah(scanningTimeMs);
- if (DEBUG && p != 0) {
- Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
- + " power=" + BatteryStats.formatCharge(p));
- }
- powerMah += p;
-
- if (remainingActiveTimeMs > 0) {
- powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
- }
+ // Attach NR Frequency Range to the key if specified.
+ switch (freqRange) {
+ case IGNORE:
+ // do nothing
+ break;
+ case ServiceState.FREQUENCY_RANGE_UNKNOWN:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+ break;
+ case ServiceState.FREQUENCY_RANGE_LOW:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
+ break;
+ case ServiceState.FREQUENCY_RANGE_MID:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
+ break;
+ case ServiceState.FREQUENCY_RANGE_HIGH:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
+ break;
+ case ServiceState.FREQUENCY_RANGE_MMWAVE:
+ key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
+ break;
+ default:
+ Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
}
- total.durationMs = radioActiveTimeMs;
- total.remainingPowerMah = powerMah;
- total.signalDurationMs = signalTimeMs;
+
+ // Attach transmission level to the key if specified.
+ switch (txLevel) {
+ case IGNORE:
+ // do nothing
+ break;
+ case 0:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
+ break;
+ case 1:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
+ break;
+ case 2:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
+ break;
+ case 3:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
+ break;
+ case 4:
+ key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
+ break;
+ default:
+ Log.w(TAG, "Unexpected transmission level : " + txLevel);
+ }
+ return key;
+ }
+
+ /**
+ * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
+ * duration.
+ */
+ public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
+ final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
+ freqRange, IGNORE);
+ final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
+ Double.NaN);
+ if (Double.isNaN(drainRateMa)) {
+ Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
+ return Double.NaN;
+ }
+
+ final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
+ if (DEBUG) {
+ Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
+ + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
+ + ModemPowerProfile.keyToString((int) rxKey));
+ }
+ return consumptionMah;
+ }
+
+ /**
+ * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+ * duration.
+ */
+ public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+ @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
+ final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
+ freqRange, txLevel);
+ final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
+ Double.NaN);
+ if (Double.isNaN(drainRateMa)) {
+ Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
+ return Double.NaN;
+ }
+
+ final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
+ if (DEBUG) {
+ Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
+ + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
+ + ModemPowerProfile.keyToString((int) txKey));
+ }
+ return consumptionMah;
}
/**
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 434cd78..392fda9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -205,4 +205,16 @@
* @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
*/
void showRearDisplayDialog(int currentBaseState);
+
+ /**
+ * Called when requested to go to fullscreen from the active split app.
+ */
+ void goToFullscreenFromSplit();
+
+ /**
+ * Enters stage split from a current running app.
+ *
+ * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+ */
+ void enterStageSplitFromRunningApp(boolean leftOrTop);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 006d888..8d71d9c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -726,6 +726,24 @@
} catch (RemoteException ex) { }
}
}
+
+ @Override
+ public void goToFullscreenFromSplit() {
+ if (mBar != null) {
+ try {
+ mBar.goToFullscreenFromSplit();
+ } catch (RemoteException ex) { }
+ }
+ }
+
+ @Override
+ public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+ if (mBar != null) {
+ try {
+ mBar.enterStageSplitFromRunningApp(leftOrTop);
+ } catch (RemoteException ex) { }
+ }
+ }
};
private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e1ab291..13111fb 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -740,7 +740,7 @@
// visible such as after the top task is finished.
for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) {
final TransitionInfo prevInfo = mTransitionInfoList.get(i);
- if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) {
+ if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.isVisibleRequested()) {
scheduleCheckActivityToBeDrawn(prevInfo.mLastLaunchedActivity, 0 /* delay */);
}
}
@@ -867,7 +867,7 @@
return;
}
if (DEBUG_METRICS) {
- Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
+ Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.isVisibleRequested()
+ " state=" + r.getState() + " finishing=" + r.finishing);
}
if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
@@ -876,7 +876,7 @@
// the tracking of launch event.
return;
}
- if (!r.mVisibleRequested || r.finishing) {
+ if (!r.isVisibleRequested() || r.finishing) {
// Check if the tracker can be cancelled because the last launched activity may be
// no longer visible.
scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
@@ -909,7 +909,7 @@
// activities in this task may be finished, invisible or drawn, so the transition event
// should be cancelled.
if (t != null && t.forAllActivities(
- a -> a.mVisibleRequested && !a.isReportedDrawn() && !a.finishing)) {
+ a -> a.isVisibleRequested() && !a.isReportedDrawn() && !a.finishing)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e099aac..aec06f0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -800,7 +800,7 @@
// it will sometimes be true a little earlier: when the activity record has
// been shown, but is still waiting for its app transition to execute
// before making its windows shown.
- boolean mVisibleRequested;
+ private boolean mVisibleRequested;
// Last visibility state we reported to the app token.
boolean reportedVisible;
@@ -3622,7 +3622,7 @@
// implied that the current finishing activity should be added into stopping list rather
// than destroy immediately.
final boolean isNextNotYetVisible = next != null
- && (!next.nowVisible || !next.mVisibleRequested);
+ && (!next.nowVisible || !next.isVisibleRequested());
// Clear last paused activity to ensure top activity can be resumed during sleeping.
if (isNextNotYetVisible && mDisplayContent.isSleeping()
@@ -4440,7 +4440,7 @@
void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
task.forAllActivities(fromActivity -> {
if (fromActivity == this) return true;
- return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity);
+ return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
});
}
@@ -5105,7 +5105,8 @@
* This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
* outside of this class should use {@link #setVisibility}.
*/
- private void setVisibleRequested(boolean visible) {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ void setVisibleRequested(boolean visible) {
if (visible == mVisibleRequested) {
return;
}
@@ -5433,25 +5434,20 @@
*/
private void postApplyAnimation(boolean visible, boolean fromTransition) {
final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
- final boolean delayed = isAnimating(PARENTS | CHILDREN,
+ final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
- if (!delayed) {
+ if (!delayed && !usingShellTransitions) {
// We aren't delayed anything, but exiting windows rely on the animation finished
// callback being called in case the ActivityRecord was pretending to be delayed,
// which we might have done because we were in closing/opening apps list.
- if (!usingShellTransitions) {
- onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
- if (visible) {
- // The token was made immediately visible, there will be no entrance animation.
- // We need to inform the client the enter animation was finished.
- mEnteringAnimation = true;
- mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
- token);
- }
- } else {
- // update wallpaper target
- setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord");
+ onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
+ if (visible) {
+ // The token was made immediately visible, there will be no entrance animation.
+ // We need to inform the client the enter animation was finished.
+ mEnteringAnimation = true;
+ mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
+ token);
}
}
@@ -5460,8 +5456,8 @@
// updated.
// If we're becoming invisible, update the client visibility if we are not running an
// animation. Otherwise, we'll update client visibility in onAnimationFinished.
- if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
- || usingShellTransitions) {
+ if (visible || usingShellTransitions
+ || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
setClientVisible(visible);
}
@@ -6549,7 +6545,7 @@
if (associatedTask == null) {
removeStartingWindow();
} else if (associatedTask.getActivity(
- r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
+ r -> r.isVisibleRequested() && !r.firstWindowDrawn) == null) {
// The last drawn activity may not be the one that owns the starting window.
final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
if (r != null) {
@@ -7442,8 +7438,10 @@
} else if (!show && mLastSurfaceShowing) {
getSyncTransaction().hide(mSurfaceControl);
}
- if (show) {
- mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction());
+ // Input sink surface is not a part of animation, so just apply in a steady state
+ // (non-sync) with pending transaction.
+ if (show && mSyncState == SYNC_STATE_NONE) {
+ mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getPendingTransaction());
}
}
if (mThumbnail != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 30c7b23..0859d40 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -92,7 +92,7 @@
public boolean isActivityVisible() {
synchronized (mService.mGlobalLock) {
- return mActivity.mVisibleRequested || mActivity.isState(RESUMED, PAUSING);
+ return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 56aae2d6..05ec3b5 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -561,7 +561,7 @@
if (rootTask == null) return false;
final RemoteTransition remote = options.getRemoteTransition();
final ActivityRecord r = rootTask.topRunningActivity();
- if (r == null || r.mVisibleRequested || !r.attachedToProcess() || remote == null
+ if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
|| !r.mActivityComponent.equals(intent.getComponent())
// Recents keeps invisible while device is locked.
|| r.mDisplayContent.isKeyguardLocked()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5c83c4f..1e06375 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2637,7 +2637,7 @@
// If the activity is visible in multi-windowing mode, it may already be on
// the top (visible to user but not the global top), then the result code
// should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
- final boolean wasTopOfVisibleRootTask = intentActivity.mVisibleRequested
+ final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
&& intentActivity.inMultiWindowMode()
&& intentActivity == mTargetRootTask.topRunningActivity();
// We only want to move to the front, if we aren't going to launch on a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7dd8770..eb04687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4386,6 +4386,13 @@
values.touchscreen,
values.uiMode);
+ // Note: certain tests currently run as platform_app which is not allowed
+ // to set debug system properties. To ensure that system properties are set
+ // only when allowed, we check the current UID.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ SystemProperties.set("debug.tracing.mcc", Integer.toString(values.mcc));
+ SystemProperties.set("debug.tracing.mnc", Integer.toString(values.mnc));
+ }
if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
final LocaleList locales = values.getLocales();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3145ab3..8a247cf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.am.HostingRecord;
import com.android.server.am.UserState;
+import com.android.server.pm.PackageManagerServiceUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
@@ -2619,12 +2620,17 @@
// ActivityStarter will acquire the lock where the places need, so execute the request
// outside of the lock.
try {
+ // We need to temporarily disable the explicit intent filter matching enforcement
+ // because Task does not store the resolved type of the intent data, causing filter
+ // mismatch in certain cases. (b/240373119)
+ PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true);
return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
null, 0, 0, options, userId, task, "startActivityFromRecents",
false /* validateIncomingUser */, null /* originatingPendingIntent */,
false /* allowBackgroundActivityStart */);
} finally {
+ PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
synchronized (mService.mGlobalLock) {
mService.continueWindowLayout();
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 798e739..b16602e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -168,6 +168,12 @@
+ "recents. Overriding back callback to recents controller callback.");
return null;
}
+
+ if (!window.isDrawn()) {
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "Focused window didn't have a valid surface drawn.");
+ return null;
+ }
}
if (window == null) {
@@ -804,7 +810,7 @@
if (activity == null) {
return;
}
- if (!activity.mVisibleRequested) {
+ if (!activity.isVisibleRequested()) {
activity.setVisibility(true);
}
activity.mLaunchTaskBehind = true;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6140508..d5802cf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -876,11 +876,11 @@
final ActivityRecord activity = w.mActivityRecord;
if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
- + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+ + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
+ " parentHidden=" + w.isParentWindowHidden());
else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
- + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+ + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
+ " parentHidden=" + w.isParentWindowHidden());
}
@@ -1705,7 +1705,7 @@
.notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
}
// The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
- final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r;
+ final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
if (handleTopActivityLaunchingInDifferentOrientation(
topCandidate, r, true /* checkOpening */)) {
// Display orientation should be deferred until the top fixed rotation is finished.
@@ -2101,13 +2101,13 @@
}
/**
- * @see DisplayWindowPolicyController#canShowTasksInRecents()
+ * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
*/
- boolean canShowTasksInRecents() {
+ boolean canShowTasksInHostDeviceRecents() {
if (mDwpcHelper == null) {
return true;
}
- return mDwpcHelper.canShowTasksInRecents();
+ return mDwpcHelper.canShowTasksInHostDeviceRecents();
}
/**
@@ -2712,7 +2712,7 @@
mWmService.mWindowsChanged = true;
// If the transition finished callback cannot match the token for some reason, make sure the
// rotated state is cleared if it is already invisible.
- if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.mVisibleRequested
+ if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
&& !mFixedRotationLaunchingApp.isVisible()
&& !mDisplayRotation.isRotatingSeamlessly()) {
clearFixedRotationLaunchingApp();
@@ -6814,10 +6814,9 @@
@Override
public boolean isRequestedVisible(@InsetsType int types) {
- if (types == ime()) {
- return getInsetsStateController().getImeSourceProvider().isImeShowing();
- }
- return (mRequestedVisibleTypes & types) != 0;
+ return ((types & ime()) != 0
+ && getInsetsStateController().getImeSourceProvider().isImeShowing())
+ || (mRequestedVisibleTypes & types) != 0;
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1fef3c2..b419f36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,7 +57,6 @@
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_WAKE;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -789,13 +788,7 @@
if (!mDisplayContent.isDefaultDisplay) {
return;
}
- if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
- && !mDisplayContent.mTransitionController.isCollecting()) {
- // Start a transition for waking. This is needed for showWhenLocked activities.
- mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
- 0 /* flags */, null /* trigger */, mDisplayContent);
- }
- mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+ mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
mAwake /* waiting */);
}
}
@@ -1202,7 +1195,6 @@
return null;
}
return (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.inset(win.mGivenContentInsets);
final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
final InsetsFrameProvider ifp = lp.providedInsets[index];
InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index eaa08fd..185e06e 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1537,6 +1537,7 @@
private int mHalfFoldSavedRotation = -1; // No saved rotation
private DeviceStateController.FoldState mFoldState =
DeviceStateController.FoldState.UNKNOWN;
+ private boolean mInHalfFoldTransition = false;
boolean overrideFrozenRotation() {
return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1544,6 +1545,7 @@
boolean shouldRevertOverriddenRotation() {
return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+ && mInHalfFoldTransition
&& mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
&& mUserRotationMode
== WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
@@ -1552,6 +1554,7 @@
int revertOverriddenRotation() {
int savedRotation = mHalfFoldSavedRotation;
mHalfFoldSavedRotation = -1;
+ mInHalfFoldTransition = false;
return savedRotation;
}
@@ -1577,16 +1580,11 @@
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
} else {
- // Revert the rotation to our saved value if we transition from HALF_FOLDED.
- if (mHalfFoldSavedRotation != -1) {
- mRotation = mHalfFoldSavedRotation;
- }
- // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
- // so we will override USER_ROTATION_LOCKED and allow a rotation).
+ mInHalfFoldTransition = true;
+ mFoldState = newState;
+ // Tell the device to update its orientation.
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
- // Once we are rotated, set mFoldstate, effectively removing the lock override.
- mFoldState = newState;
}
}
}
@@ -1683,6 +1681,7 @@
private static class RotationHistory {
private static final int MAX_SIZE = 8;
+ private static final int NO_FOLD_CONTROLLER = -2;
private static class Record {
final @Surface.Rotation int mFromRotation;
final @Surface.Rotation int mToRotation;
@@ -1694,6 +1693,9 @@
final String mLastOrientationSource;
final @ActivityInfo.ScreenOrientation int mSourceOrientation;
final long mTimestamp = System.currentTimeMillis();
+ final int mHalfFoldSavedRotation;
+ final boolean mInHalfFoldTransition;
+ final DeviceStateController.FoldState mFoldState;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
mFromRotation = fromRotation;
@@ -1719,6 +1721,15 @@
mLastOrientationSource = null;
mSourceOrientation = SCREEN_ORIENTATION_UNSET;
}
+ if (dr.mFoldController != null) {
+ mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
+ mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
+ mFoldState = dr.mFoldController.mFoldState;
+ } else {
+ mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
+ mInHalfFoldTransition = false;
+ mFoldState = DeviceStateController.FoldState.UNKNOWN;
+ }
}
void dump(String prefix, PrintWriter pw) {
@@ -1735,6 +1746,12 @@
if (mNonDefaultRequestingTaskDisplayArea != null) {
pw.println(prefix + " requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
}
+ if (mHalfFoldSavedRotation != NO_FOLD_CONTROLLER) {
+ pw.println(prefix + " halfFoldSavedRotation="
+ + mHalfFoldSavedRotation
+ + " mInHalfFoldTransition=" + mInHalfFoldTransition
+ + " mFoldState=" + mFoldState);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 6f821b5..69fd00c 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -153,13 +153,13 @@
}
/**
- * @see DisplayWindowPolicyController#canShowTasksInRecents()
+ * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
*/
- public final boolean canShowTasksInRecents() {
+ public final boolean canShowTasksInHostDeviceRecents() {
if (mDisplayWindowPolicyController == null) {
return true;
}
- return mDisplayWindowPolicyController.canShowTasksInRecents();
+ return mDisplayWindowPolicyController.canShowTasksInHostDeviceRecents();
}
/**
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 7bb036d..bd83794 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -191,7 +191,7 @@
if (!r.attachedToProcess()) {
makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
resumeTopActivity && isTop, r);
- } else if (r.mVisibleRequested) {
+ } else if (r.isVisibleRequested()) {
// If this activity is already visible, then there is nothing to do here.
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
@@ -244,7 +244,7 @@
// invisible. If the app is already visible, it must have died while it was visible. In this
// case, we'll show the dead window but will not restart the app. Otherwise we could end up
// thrashing.
- if (!isTop && r.mVisibleRequested && !r.isState(INITIALIZING)) {
+ if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
return;
}
@@ -256,7 +256,7 @@
if (r != starting) {
r.startFreezingScreenLocked(configChanges);
}
- if (!r.mVisibleRequested || r.mLaunchTaskBehind) {
+ if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
if (DEBUG_VISIBILITY) {
Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1567fa7..e9badef 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -37,6 +37,7 @@
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -176,7 +177,7 @@
final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
|| (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
if (aodRemoved) {
- updateDeferWakeTransition(false /* waiting */);
+ updateDeferTransitionForAod(false /* waiting */);
}
if (!keyguardChanged && !aodChanged) {
setWakeTransitionReady();
@@ -533,24 +534,25 @@
private final Runnable mResetWaitTransition = () -> {
synchronized (mWindowManager.mGlobalLock) {
- updateDeferWakeTransition(false /* waiting */);
+ updateDeferTransitionForAod(false /* waiting */);
}
};
- void updateDeferWakeTransition(boolean waiting) {
+ // Defer transition until AOD dismissed.
+ void updateDeferTransitionForAod(boolean waiting) {
if (waiting == mWaitingForWakeTransition) {
return;
}
- if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+ if (!mService.getTransitionController().isCollecting()) {
return;
}
- // if aod is showing, defer the wake transition until aod state changed.
+ // if AOD is showing, defer the wake transition until AOD state changed.
if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
mWaitingForWakeTransition = true;
mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
} else if (!waiting) {
- // dismiss the deferring if the aod state change or cancel awake.
+ // dismiss the deferring if the AOD state change or cancel awake.
mWaitingForWakeTransition = false;
mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
mWindowManager.mH.removeCallbacks(mResetWaitTransition);
@@ -648,6 +650,12 @@
mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
&& !mOccluded && !mKeyguardGoingAway
&& mDismissingKeyguardActivity != null;
+ if (mOccluded && mKeyguardShowing && !display.isSleeping() && !top.fillsParent()
+ && display.mWallpaperController.getWallpaperTarget() == null) {
+ // The occluding activity may be translucent or not fill screen. Then let wallpaper
+ // to check whether it should set itself as target to avoid blank background.
+ display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ }
if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
&& mTopTurnScreenOnActivity != null
@@ -657,10 +665,18 @@
mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
}
+ boolean hasChange = false;
if (lastOccluded != mOccluded) {
controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+ hasChange = true;
} else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
controller.handleKeyguardGoingAwayChanged(display);
+ hasChange = true;
+ }
+ // Collect the participates for shell transition, so that transition won't happen too
+ // early since the transition was set ready.
+ if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+ display.mTransitionController.collect(top);
}
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bb4c482..bcea6f4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -673,6 +673,12 @@
+ getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
pw.println(prefix + " letterboxVerticalPositionMultiplier="
+ getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ pw.println(prefix + " letterboxPositionForHorizontalReachability="
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ pw.println(prefix + " letterboxPositionForVerticalReachability="
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
pw.println(prefix + " fixedOrientationLetterboxAspectRatio="
+ mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
pw.println(prefix + " defaultMinAspectRatioForUnresizableApps="
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1fc061b..c827062 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1393,7 +1393,7 @@
// Ignore the task if it is started on a display which is not allow to show its tasks on
// Recents.
if (task.getDisplayContent() != null
- && !task.getDisplayContent().canShowTasksInRecents()) {
+ && !task.getDisplayContent().canShowTasksInHostDeviceRecents()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ffe3374..be90588 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -112,7 +112,7 @@
mTargetActivityType);
ActivityRecord targetActivity = getTargetActivity(targetRootTask);
if (targetActivity != null) {
- if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) {
+ if (targetActivity.isVisibleRequested() || targetActivity.isTopRunningActivity()) {
// The activity is ready.
return;
}
@@ -195,7 +195,7 @@
// Send launch hint if we are actually launching the target. If it's already visible
// (shouldn't happen in general) we don't need to send it.
- if (targetActivity == null || !targetActivity.mVisibleRequested) {
+ if (targetActivity == null || !targetActivity.isVisibleRequested()) {
mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
true /* forceSend */, targetActivity);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 64cca87..1ee4d6b 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2619,7 +2619,7 @@
final ArrayList<Task> addedTasks = new ArrayList<>();
forAllActivities((r) -> {
final Task task = r.getTask();
- if (r.mVisibleRequested && r.mStartingData == null && !addedTasks.contains(task)) {
+ if (r.isVisibleRequested() && r.mStartingData == null && !addedTasks.contains(task)) {
r.showStartingWindow(true /*taskSwitch*/);
addedTasks.add(task);
}
@@ -2644,7 +2644,7 @@
forAllLeafTasks(task -> {
final int oldRank = task.mLayerRank;
final ActivityRecord r = task.topRunningActivityLocked();
- if (r != null && r.mVisibleRequested) {
+ if (r != null && r.isVisibleRequested()) {
task.mLayerRank = ++mTmpTaskLayerRank;
} else {
task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 356cbda..a74a4b0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -498,6 +498,12 @@
*/
boolean mInRemoveTask;
+ /**
+ * When set, disassociate the leaf task if relaunched and reparented it to TDA as root task if
+ * possible.
+ */
+ boolean mReparentLeafTaskIfRelaunch;
+
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -2161,7 +2167,7 @@
}
private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
- if (!isLeafTask() || !canStartChangeTransition()) {
+ if (!(isLeafTask() || mCreatedByOrganizer) || !canStartChangeTransition()) {
return false;
}
final int newWinMode = getWindowingMode();
@@ -2449,7 +2455,7 @@
final String myReason = reason + " adjustFocusToNextFocusableTask";
final ActivityRecord top = focusableTask.topRunningActivity();
- if (focusableTask.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
+ if (focusableTask.isActivityTypeHome() && (top == null || !top.isVisibleRequested())) {
// If we will be focusing on the root home task next and its current top activity isn't
// visible, then use the move the root home task to top to make the activity visible.
focusableTask.getDisplayArea().moveHomeActivityToTop(myReason);
@@ -2762,7 +2768,7 @@
*/
private static void getMaxVisibleBounds(ActivityRecord token, Rect out, boolean[] foundTop) {
// skip hidden (or about to hide) apps
- if (token.mIsExiting || !token.isClientVisible() || !token.mVisibleRequested) {
+ if (token.mIsExiting || !token.isClientVisible() || !token.isVisibleRequested()) {
return;
}
final WindowState win = token.findMainWindow();
@@ -3071,7 +3077,7 @@
* this activity.
*/
ActivityRecord getTopVisibleActivity() {
- return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.mVisibleRequested);
+ return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.isVisibleRequested());
}
/**
@@ -5720,7 +5726,7 @@
forAllActivities(r -> {
if (!r.info.packageName.equals(packageName)) return;
r.forceNewConfig = true;
- if (starting != null && r == starting && r.mVisibleRequested) {
+ if (starting != null && r == starting && r.isVisibleRequested()) {
r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
}
});
@@ -6064,6 +6070,12 @@
}
}
+ void setReparentLeafTaskIfRelaunch(boolean reparentLeafTaskIfRelaunch) {
+ if (isOrganized()) {
+ mReparentLeafTaskIfRelaunch = reparentLeafTaskIfRelaunch;
+ }
+ }
+
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
@WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index f3ed937..25b58fa 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -899,15 +899,16 @@
}
} else if (candidateTask != null) {
final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
- final Task launchRootTask = getLaunchRootTask(resolvedWindowingMode, activityType,
+ final Task launchParentTask = getLaunchRootTask(resolvedWindowingMode, activityType,
options, sourceTask, launchFlags, candidateTask);
- if (launchRootTask != null) {
+ if (launchParentTask != null) {
if (candidateTask.getParent() == null) {
- launchRootTask.addChild(candidateTask, position);
- } else if (candidateTask.getParent() != launchRootTask) {
- candidateTask.reparent(launchRootTask, position);
+ launchParentTask.addChild(candidateTask, position);
+ } else if (candidateTask.getParent() != launchParentTask) {
+ candidateTask.reparent(launchParentTask, position);
}
- } else if (candidateTask.getDisplayArea() != this) {
+ } else if (candidateTask.getDisplayArea() != this
+ || candidateTask.getRootTask().mReparentLeafTaskIfRelaunch) {
if (candidateTask.getParent() == null) {
addChild(candidateTask, position);
} else {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d69d949..66b868a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -346,7 +346,7 @@
}
void process(ActivityRecord start, boolean preserveWindow) {
- if (start == null || !start.mVisibleRequested) {
+ if (start == null || !start.isVisibleRequested()) {
return;
}
reset(preserveWindow);
@@ -1356,7 +1356,7 @@
if (next.attachedToProcess()) {
if (DEBUG_SWITCH) {
Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
- + " visibleRequested=" + next.mVisibleRequested);
+ + " visibleRequested=" + next.isVisibleRequested());
}
// If the previous activity is translucent, force a visibility update of
@@ -1370,7 +1370,7 @@
|| mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
// This activity is now becoming visible.
- if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+ if (!next.isVisibleRequested() || next.stopped || lastActivityTranslucent) {
next.app.addToPendingTop();
next.setVisibility(true);
}
@@ -1421,7 +1421,7 @@
// Do over!
mTaskSupervisor.scheduleResumeTopActivities();
}
- if (!next.mVisibleRequested || next.stopped) {
+ if (!next.isVisibleRequested() || next.stopped) {
next.setVisibility(true);
}
next.completeResumeLocked();
@@ -1732,7 +1732,7 @@
} else if (prev.attachedToProcess()) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,
- prev.mVisibleRequested);
+ prev.isVisibleRequested());
if (prev.deferRelaunchUntilPaused) {
// Complete the deferred relaunch that was waiting for pause to complete.
ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
@@ -1742,7 +1742,7 @@
// We can't clobber it, because the stop confirmation will not be handled.
// We don't need to schedule another stop, we only need to let it happen.
prev.setState(STOPPING, "completePausedLocked");
- } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+ } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
// Clear out any deferred client hide we might currently have.
prev.setDeferHidingClient(false);
// If we were visible then resumeTopActivities will release resources before
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index da73fad..ad46770 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -165,9 +165,11 @@
}
// If the launch windowing mode is still undefined, inherit from the target task if the
// task is already on the right display area (otherwise, the task may be on a different
- // display area that has incompatible windowing mode).
+ // display area that has incompatible windowing mode or the task organizer request to
+ // disassociate the leaf task if relaunched and reparented it to TDA as root task).
if (launchMode == WINDOWING_MODE_UNDEFINED
- && task != null && task.getTaskDisplayArea() == suggestedDisplayArea) {
+ && task != null && task.getTaskDisplayArea() == suggestedDisplayArea
+ && !task.getRootTask().mReparentLeafTaskIfRelaunch) {
launchMode = task.getWindowingMode();
if (DEBUG) {
appendLog("inherit-from-task="
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a2e877..274d7ff 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -691,6 +691,8 @@
if (mainWindow == null || mainWindow.mRemoved) {
removalInfo.playRevealAnimation = false;
} else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.roundedCornerRadius =
+ topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 29c98b9..c1b9e662 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,29 +16,14 @@
package com.android.server.wm;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
-import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
-
-import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES;
-import static com.android.internal.policy.DecorView.getNavigationBarRect;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.RecordingCanvas;
@@ -59,12 +44,11 @@
import android.view.WindowInsetsController.Appearance;
import android.view.WindowManager.LayoutParams;
import android.window.ScreenCapture;
+import android.window.SnapshotDrawerUtils;
import android.window.TaskSnapshot;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.policy.DecorView;
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.wm.utils.InsetUtils;
@@ -585,7 +569,8 @@
final Rect taskBounds = task.getBounds();
final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
- final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
+ final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter =
+ new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
final int taskWidth = taskBounds.width();
@@ -599,7 +584,7 @@
final RecordingCanvas c = node.start(width, height);
c.drawColor(color);
decorPainter.setInsets(systemBarInsets);
- decorPainter.drawDecors(c /* statusBarExcludeFrame */);
+ decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawnFrame */);
node.end(c);
final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
if (hwBitmap == null) {
@@ -736,92 +721,4 @@
pw.println(prefix + "mTaskSnapshotEnabled=" + mTaskSnapshotEnabled);
mCache.dump(pw, prefix);
}
-
- /**
- * Helper class to draw the background of the system bars in regions the task snapshot isn't
- * filling the window.
- */
- static class SystemBarBackgroundPainter {
-
- private final Paint mStatusBarPaint = new Paint();
- private final Paint mNavigationBarPaint = new Paint();
- private final int mStatusBarColor;
- private final int mNavigationBarColor;
- private final int mWindowFlags;
- private final int mWindowPrivateFlags;
- private final float mScale;
- private final @Type.InsetsType int mRequestedVisibleTypes;
- private final Rect mSystemBarInsets = new Rect();
-
- SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int appearance,
- ActivityManager.TaskDescription taskDescription, float scale,
- @Type.InsetsType int requestedVisibleTypes) {
- mWindowFlags = windowFlags;
- mWindowPrivateFlags = windowPrivateFlags;
- mScale = scale;
- final Context context = ActivityThread.currentActivityThread().getSystemUiContext();
- final int semiTransparent = context.getColor(
- R.color.system_bar_background_semi_transparent);
- mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
- semiTransparent, taskDescription.getStatusBarColor(), appearance,
- APPEARANCE_LIGHT_STATUS_BARS,
- taskDescription.getEnsureStatusBarContrastWhenTransparent());
- mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
- FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
- taskDescription.getNavigationBarColor(), appearance,
- APPEARANCE_LIGHT_NAVIGATION_BARS,
- taskDescription.getEnsureNavigationBarContrastWhenTransparent()
- && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
- mStatusBarPaint.setColor(mStatusBarColor);
- mNavigationBarPaint.setColor(mNavigationBarColor);
- mRequestedVisibleTypes = requestedVisibleTypes;
- }
-
- void setInsets(Rect systemBarInsets) {
- mSystemBarInsets.set(systemBarInsets);
- }
-
- int getStatusBarColorViewHeight() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- if (STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mStatusBarColor, mWindowFlags, forceBarBackground)) {
- return (int) (mSystemBarInsets.top * mScale);
- } else {
- return 0;
- }
- }
-
- private boolean isNavigationBarColorViewVisible() {
- final boolean forceBarBackground =
- (mWindowPrivateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0;
- return NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES.isVisible(
- mRequestedVisibleTypes, mNavigationBarColor, mWindowFlags, forceBarBackground);
- }
-
- void drawDecors(Canvas c) {
- drawStatusBarBackground(c, getStatusBarColorViewHeight());
- drawNavigationBarBackground(c);
- }
-
- @VisibleForTesting
- void drawStatusBarBackground(Canvas c,
- int statusBarHeight) {
- if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0) {
- final int rightInset = (int) (mSystemBarInsets.right * mScale);
- c.drawRect(0, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint);
- }
- }
-
- @VisibleForTesting
- void drawNavigationBarBackground(Canvas c) {
- final Rect navigationBarRect = new Rect();
- getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect,
- mScale);
- final boolean visible = isNavigationBarColorViewVisible();
- if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) {
- c.drawRect(navigationBarRect, mNavigationBarPaint);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2b11d54..2737456 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -639,8 +639,8 @@
if (target.asDisplayContent() != null || target.asActivityRecord() != null) {
t.setCrop(targetLeash, null /* crop */);
} else {
- // Crop to the requested bounds.
- final Rect clipRect = target.getRequestedOverrideBounds();
+ // Crop to the resolved override bounds.
+ final Rect clipRect = target.getResolvedOverrideBounds();
t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
}
t.setCornerRadius(targetLeash, 0);
@@ -992,7 +992,7 @@
// show here in the same way that we manually hide in finishTransaction.
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || !ar.mVisibleRequested) continue;
+ if (ar == null || !ar.isVisibleRequested()) continue;
transaction.show(ar.getSurfaceControl());
// Also manually show any non-reported parents. This is necessary in a few cases
@@ -1246,7 +1246,7 @@
ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
for (int i = mParticipants.size() - 1; i >= 0; --i) {
ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
- if (r == null || !r.mVisibleRequested) continue;
+ if (r == null || !r.isVisibleRequested()) continue;
int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
// At this point, r is "ready", but if it's not "ALL ready" then it is probably only
// ready due to starting-window.
@@ -1617,8 +1617,11 @@
WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
- // make leash based on highest (z-order) direct child of ancestor with a participant.
- WindowContainer leashReference = sortedTargets.get(0);
+ // Make leash based on highest (z-order) direct child of ancestor with a participant.
+ // TODO(b/261418859): Handle the case when the target contains window containers which
+ // belong to a different display. As a workaround we use topApp, from which wallpaper
+ // window container is removed, instead of sortedTargets here.
+ WindowContainer leashReference = topApp;
while (leashReference.getParent() != ancestor) {
leashReference = leashReference.getParent();
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 99527b1..cf541fc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -38,6 +38,7 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITransitionMetricsReporter;
import android.window.ITransitionPlayer;
@@ -116,6 +117,8 @@
*/
boolean mBuildingFinishLayers = false;
+ private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction();
+
TransitionController(ActivityTaskManagerService atm,
TaskSnapshotController taskSnapshotController,
TransitionTracer transitionTracer) {
@@ -619,8 +622,16 @@
private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
if (mTransitionPlayerProc == null) return;
if (isPlaying) {
+ mWakeT.setEarlyWakeupStart();
+ mWakeT.apply();
+ // Usually transitions put quite a load onto the system already (with all the things
+ // happening in app), so pause task snapshot persisting to not increase the load.
+ mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
mTransitionPlayerProc.setRunningRemoteAnimation(true);
} else if (mPlayingTransitions.isEmpty()) {
+ mWakeT.setEarlyWakeupEnd();
+ mWakeT.apply();
+ mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
mTransitionPlayerProc.setRunningRemoteAnimation(false);
mRemotePlayer.clear();
return;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3b30dd1..5de143d9 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -725,9 +725,9 @@
}
final boolean newTargetHidden = wallpaperTarget.mActivityRecord != null
- && !wallpaperTarget.mActivityRecord.mVisibleRequested;
+ && !wallpaperTarget.mActivityRecord.isVisibleRequested();
final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
- && !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
+ && !prevWallpaperTarget.mActivityRecord.isVisibleRequested();
ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
+ "old: %s hidden=%b new: %s hidden=%b",
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6ee30bb..32cfb70 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -136,7 +136,7 @@
recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
} else if ((wallpaperTarget.mActivityRecord == null
// Ignore invisible activity because it may be moving to background.
- || wallpaperTarget.mActivityRecord.mVisibleRequested)
+ || wallpaperTarget.mActivityRecord.isVisibleRequested())
&& wallpaperTarget.mToken.hasFixedRotationTransform()) {
// If the wallpaper target has a fixed rotation, we want the wallpaper to follow its
// rotation
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 2838304..46a30fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1221,6 +1221,12 @@
pw.println("Default position for vertical reachability: "
+ LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+ pw.println("Current position for horizontal reachability:"
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+ pw.println("Current position for vertical reachability:"
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
pw.println("Is education enabled: "
+ mLetterboxConfiguration.getIsEducationEnabled());
pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index aa1cf56..738adc3 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -43,6 +43,7 @@
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_SET_REPARENT_LEAF_TASK_IF_RELAUNCH;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
@@ -1233,7 +1234,7 @@
WindowContainer.fromBinder(hop.getContainer())
.removeLocalInsetsSourceProvider(hop.getInsetsTypes());
break;
- case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP:
+ case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: {
final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
if (container == null || container.asDisplayArea() == null
|| !container.isAttached()) {
@@ -1244,7 +1245,26 @@
container.setAlwaysOnTop(hop.isAlwaysOnTop());
effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
-
+ }
+ case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH: {
+ final WindowContainer container = WindowContainer.fromBinder(hop.getContainer());
+ final Task task = container != null ? container.asTask() : null;
+ if (task == null || !task.isAttached()) {
+ Slog.e(TAG, "Attempt to operate on unknown or detached container: "
+ + container);
+ break;
+ }
+ if (!task.mCreatedByOrganizer) {
+ throw new UnsupportedOperationException(
+ "Cannot set reparent leaf task flag on non-organized task : " + task);
+ }
+ if (!task.isRootTask()) {
+ throw new UnsupportedOperationException(
+ "Cannot set reparent leaf task flag on non-root task : " + task);
+ }
+ task.setReparentLeafTaskIfRelaunch(hop.isReparentLeafTaskIfRelaunch());
+ break;
+ }
}
return effects;
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8f63e93..d2cd8f8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -765,7 +765,7 @@
// - no longer visible OR
// - not focusable (in PiP mode for instance)
if (topDisplay == null
- || !mPreQTopResumedActivity.mVisibleRequested
+ || !mPreQTopResumedActivity.isVisibleRequested()
|| !mPreQTopResumedActivity.isFocusable()) {
canUpdate = true;
}
@@ -874,7 +874,7 @@
// to those activities that are part of the package whose app-specific settings changed
if (packageName.equals(r.packageName)
&& r.applyAppSpecificConfig(nightMode, localesOverride)
- && r.mVisibleRequested) {
+ && r.isVisibleRequested()) {
r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
}
}
@@ -956,7 +956,7 @@
}
// Don't consider any activities that are currently not in a state where they
// can be destroyed.
- if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
+ if (r.isVisibleRequested() || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
|| r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
continue;
@@ -1002,7 +1002,7 @@
final int displayId = r.getDisplayId();
final Context c = root.getDisplayUiContext(displayId);
- if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
+ if (c != null && r.isVisibleRequested() && !displayContexts.contains(c)) {
displayContexts.add(c);
}
}
@@ -1070,7 +1070,7 @@
if (task != null && task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
}
- if (r.mVisibleRequested) {
+ if (r.isVisibleRequested()) {
if (r.isState(RESUMED)) {
stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
}
@@ -1282,7 +1282,7 @@
}
for (int i = activities.size() - 1; i >= 0; i--) {
final ActivityRecord r = activities.get(i);
- if (r.mVisibleRequested || r.isVisible()) {
+ if (r.isVisibleRequested() || r.isVisible()) {
// While an activity launches a new activity, it's possible that the old activity
// is already requested to be hidden (mVisibleRequested=false), but this visibility
// is not yet committed, so isVisible()=true.
@@ -1503,7 +1503,7 @@
Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
overrideConfig.assetsSeq = assetSeq;
r.onRequestedOverrideConfigurationChanged(overrideConfig);
- if (r.mVisibleRequested) {
+ if (r.isVisibleRequested()) {
r.ensureActivityConfiguration(0, true);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 73759d3..c6578ef 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1957,7 +1957,7 @@
*/
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
- return (mActivityRecord == null || mActivityRecord.mVisibleRequested
+ return (mActivityRecord == null || mActivityRecord.isVisibleRequested()
|| mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
}
@@ -1994,7 +1994,7 @@
final ActivityRecord atoken = mActivityRecord;
return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& isVisibleByPolicy() && !isParentWindowHidden()
- && (atoken == null || atoken.mVisibleRequested)
+ && (atoken == null || atoken.isVisibleRequested())
&& !mAnimatingExit && !mDestroying;
}
@@ -2101,7 +2101,7 @@
boolean isDisplayed() {
final ActivityRecord atoken = mActivityRecord;
return isDrawn() && isVisibleByPolicy()
- && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
+ && ((!isParentWindowHidden() && (atoken == null || atoken.isVisibleRequested()))
|| isAnimating(TRANSITION | PARENTS));
}
@@ -2123,7 +2123,7 @@
// a layout since they can request relayout when client visibility is false.
// TODO (b/157682066) investigate if we can clean up isVisible
|| (atoken == null && !(wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()))
- || (atoken != null && !atoken.mVisibleRequested)
+ || (atoken != null && !atoken.isVisibleRequested())
|| isParentWindowGoneForLayout()
|| (mAnimatingExit && !isAnimatingLw())
|| mDestroying;
@@ -2170,7 +2170,7 @@
return;
}
if (mActivityRecord != null) {
- if (!mActivityRecord.mVisibleRequested) return;
+ if (!mActivityRecord.isVisibleRequested()) return;
if (mActivityRecord.allDrawn) {
// The allDrawn of activity is reset when the visibility is changed to visible, so
// the content should be ready if allDrawn is set.
@@ -2743,7 +2743,7 @@
+ " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
if (mActivityRecord != null) {
Slog.i(TAG_WM, " mActivityRecord.visibleRequested="
- + mActivityRecord.mVisibleRequested);
+ + mActivityRecord.isVisibleRequested());
}
}
}
@@ -3219,7 +3219,7 @@
}
return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
- && mActivityRecord.mVisibleRequested;
+ && mActivityRecord.isVisibleRequested();
}
/**
@@ -3875,7 +3875,7 @@
// the client erroneously accepting a configuration that would have otherwise caused an
// activity restart. We instead hand back the last reported {@link MergedConfiguration}.
if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
- || mActivityRecord.mVisibleRequested))) {
+ || mActivityRecord.isVisibleRequested()))) {
final Configuration globalConfig = getProcessGlobalConfiguration();
final Configuration overrideConfig = getMergedOverrideConfiguration();
outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
@@ -4734,7 +4734,7 @@
+ " during animation: policyVis=" + isVisibleByPolicy()
+ " parentHidden=" + isParentWindowHidden()
+ " tok.visibleRequested="
- + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+ + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
+ " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
+ " animating=" + isAnimating(TRANSITION | PARENTS)
+ " tok animating="
@@ -5195,7 +5195,7 @@
+ " pv=" + isVisibleByPolicy()
+ " mDrawState=" + mWinAnimator.mDrawState
+ " ph=" + isParentWindowHidden()
- + " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+ + " th=" + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
+ " a=" + isAnimating(TRANSITION | PARENTS));
}
}
diff --git a/services/core/java/com/android/server/wm/utils/StateMachine.java b/services/core/java/com/android/server/wm/utils/StateMachine.java
new file mode 100644
index 0000000..91a5dc4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/StateMachine.java
@@ -0,0 +1,280 @@
+/*
+ * 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.wm.utils;
+
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * Simple hierarchical state machine.
+ *
+ * The state is represented by an integer value. The root state has a value {@code 0x0}, and top
+ * level state has a value in range {@code 0x1} to {@code 0xF}. To indicate a state B is a sub state
+ * of a state A, assign an integer state_value(B) = state_value(A) << 4 + (0x0 .. 0xF).
+ */
+public class StateMachine {
+ private static final String TAG = "StateMachine";
+
+ /**
+ * Interface for implementing state specific actions.
+ */
+ public interface Handler {
+ /**
+ * Called when state machine changes its state to this state.
+ */
+ default void enter() {}
+
+ /**
+ * Called when state machine changes its state from this state to other state.
+ */
+ default void exit() {}
+
+ /**
+ * @param event type of this event.
+ * @param param parameter passed to {@link StateMachine#handle(int, Object)}
+ * @return {@code true} if the event was handled in this handler, so we don't need to
+ * check the parent state. Otherwise, handle() of the parent state is triggered.
+ */
+ default boolean handle(int event, @Nullable Object param) {
+ return false;
+ }
+ }
+
+ /**
+ * The most recent state requested by transit() call.
+ *
+ * @note When transit() is called recursively, this might not be same value as mState until
+ * transit() finishes.
+ */
+ private int mLastRequestedState;
+
+ /**
+ * The current state of this state machine.
+ */
+ private int mState;
+
+ private final IntArray mTmp = new IntArray();
+ private final SparseArray<Handler> mStateHandlers = new SparseArray<>();
+
+ /**
+ * Actions which need to execute to finish requested transition.
+ */
+ private final Queue<Command> mCommands = new ArrayDeque<>();
+
+ protected static class Command {
+ static final int COMMIT = 1;
+ static final int ENTER = 2;
+ static final int EXIT = 3;
+
+ final int mType;
+ final int mState;
+
+ private Command(int type, @IntRange(from = 0) int state) {
+ mType = type;
+ AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+ mState = state;
+ }
+
+ static Command newCommit(int state) {
+ return new Command(COMMIT, state);
+ }
+
+ static Command newEnter(int state) {
+ return new Command(ENTER, state);
+ }
+
+ static Command newExit(int state) {
+ return new Command(EXIT, state);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Command{ type: ");
+ switch (mType) {
+ case COMMIT:
+ sb.append("commit");
+ break;
+ case ENTER:
+ sb.append("enter");
+ break;
+ case EXIT:
+ sb.append("exit");
+ break;
+ default:
+ sb.append("UNKNOWN(");
+ sb.append(mType);
+ sb.append(")");
+ break;
+ }
+ sb.append(" state: ");
+ sb.append(Integer.toHexString(mState));
+ sb.append(" }");
+ return sb.toString();
+ }
+ }
+
+ public StateMachine() {
+ this(0);
+ }
+
+ public StateMachine(@IntRange(from = 0) int initialState) {
+ mState = initialState;
+ AnnotationValidations.validate(IntRange.class, null, initialState, "from", 0);
+ mLastRequestedState = initialState;
+ }
+
+ /**
+ * @see #mLastRequestedState
+ */
+ public int getState() {
+ return mLastRequestedState;
+ }
+
+ protected int getCurrentState() {
+ return mState;
+ }
+
+ protected Command[] getCommands() {
+ final Command[] commands = new Command[mCommands.size()];
+ mCommands.toArray(commands);
+ return commands;
+ }
+
+ /**
+ * Add a handler for a specific state.
+ *
+ * @param state State which the given handler processes.
+ * @param handler A handler which runs entry, exit actions and processes events.
+ * @return Previous state handler if it's already registered, or {@code null}.
+ */
+ @Nullable public Handler addStateHandler(int state, @Nullable Handler handler) {
+ final Handler handlerOld = mStateHandlers.get(state);
+ mStateHandlers.put(state, handler);
+ return handlerOld;
+ }
+
+ /**
+ * Process an event. Search handler for a given event and {@link Handler#handle(int)}. If the
+ * handler cannot handle the event, delegate it to a handler for a parent of the given state.
+ *
+ * @param event Type of an event.
+ */
+ public void handle(int event, @Nullable Object param) {
+ int state = mState;
+ while (state != 0) {
+ final Handler h = mStateHandlers.get(state);
+ if (h != null && h.handle(event, param)) {
+ return;
+ }
+ state >>= 4;
+ }
+ }
+
+ protected void enter(@IntRange(from = 0) int state) {
+ AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+ final Handler h = mStateHandlers.get(state);
+ if (h != null) {
+ h.enter();
+ }
+ }
+
+ protected void exit(@IntRange(from = 0) int state) {
+ AnnotationValidations.validate(IntRange.class, null, state, "from", 0);
+ final Handler h = mStateHandlers.get(state);
+ if (h != null) {
+ h.exit();
+ }
+ }
+
+ /**
+ * @return {@code true} if a given sub state is a descendant of a given super state.
+ */
+ public static boolean isIn(int subState, int superState) {
+ while (subState > superState) {
+ subState >>= 4;
+ }
+ return subState == superState;
+ }
+
+ /**
+ * Check if the last requested state is a sub state of a given state.
+ *
+ * @return {@code true} if the last requested state (via {@link #transit(int)}) is a sub state
+ * of a given state.
+ */
+ public boolean isIn(int state) {
+ return isIn(mLastRequestedState, state);
+ }
+
+ /**
+ * Change state to the requested state.
+ *
+ * @param newState The new state that the state machine should be changed.
+ */
+ public void transit(@IntRange(from = 0) int newState) {
+ AnnotationValidations.validate(IntRange.class, null, newState, "from", 0);
+
+ // entry and exit action might start another transition, so this transit() function can be
+ // called recursively. In order to guarantee entry and exit actions in expected order,
+ // we first compute the sequence and push them into a queue, then process them later.
+ mCommands.add(Command.newCommit(newState));
+ if (mLastRequestedState == newState) {
+ mCommands.add(Command.newExit(newState));
+ mCommands.add(Command.newEnter(newState));
+ } else {
+ // mLastRequestedState to least common ancestor
+ for (int s = mLastRequestedState; !isIn(newState, s); s >>= 4) {
+ mCommands.add(Command.newExit(s));
+ }
+ // least common ancestor to newState
+ mTmp.clear();
+ for (int s = newState; !isIn(mLastRequestedState, s); s >>= 4) {
+ mTmp.add(s);
+ }
+ for (int i = mTmp.size() - 1; i >= 0; --i) {
+ mCommands.add(Command.newEnter(mTmp.get(i)));
+ }
+ }
+ mLastRequestedState = newState;
+ while (!mCommands.isEmpty()) {
+ final Command cmd = mCommands.remove();
+ switch (cmd.mType) {
+ case Command.EXIT:
+ exit(cmd.mState);
+ break;
+ case Command.ENTER:
+ enter(cmd.mState);
+ break;
+ case Command.COMMIT:
+ mState = cmd.mState;
+ break;
+ default:
+ Slog.e(TAG, "Unknown command type: " + cmd.mType);
+ break;
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/utils/TEST_MAPPING b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
new file mode 100644
index 0000000..aa69d2a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/TEST_MAPPING
@@ -0,0 +1,18 @@
+{
+ "presubmit": [
+ {
+ "name": "WmTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.wm.utils"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index f431250..e661688 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -31,6 +31,9 @@
"BroadcastRadio/convert.cpp",
"BroadcastRadio/regions.cpp",
"stats/SurfaceFlingerPuller.cpp",
+ "tvinput/BufferProducerThread.cpp",
+ "tvinput/JTvInputHal.cpp",
+ "tvinput/TvInputHal_hidl.cpp",
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
@@ -150,6 +153,7 @@
"libdataloader",
"libincfs",
"android.hardware.audio.common@2.0",
+ "android.media.audio.common.types-V1-ndk",
"android.hardware.broadcastradio@1.0",
"android.hardware.broadcastradio@1.1",
"android.hardware.contexthub@1.0",
@@ -176,6 +180,7 @@
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
"android.hardware.tv.input@1.0",
+ "android.hardware.tv.input-V1-ndk",
"android.hardware.vibrator-V2-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
@@ -197,6 +202,7 @@
static_libs: [
"android.hardware.broadcastradio@common-utils-1x-lib",
+ "libaidlcommonsupport",
],
product_variables: {
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index a5311f3..a8d2f4e 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -18,577 +18,15 @@
//#define LOG_NDEBUG 0
-#include "android_os_MessageQueue.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/android_view_Surface.h"
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
+#include "tvinput/JTvInputHal.h"
-#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
-#include <android/hardware/tv/input/1.0/ITvInput.h>
-#include <android/hardware/tv/input/1.0/types.h>
-#include <gui/Surface.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/Log.h>
-#include <utils/Looper.h>
-#include <utils/NativeHandle.h>
-#include <hardware/tv_input.h>
-
-using ::android::hardware::audio::common::V2_0::AudioDevice;
-using ::android::hardware::tv::input::V1_0::ITvInput;
-using ::android::hardware::tv::input::V1_0::ITvInputCallback;
-using ::android::hardware::tv::input::V1_0::Result;
-using ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
-using ::android::hardware::tv::input::V1_0::TvInputEvent;
-using ::android::hardware::tv::input::V1_0::TvInputEventType;
-using ::android::hardware::tv::input::V1_0::TvInputType;
-using ::android::hardware::tv::input::V1_0::TvStreamConfig;
-using ::android::hardware::Return;
-using ::android::hardware::Void;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::hidl_string;
+gTvInputHalClassInfoType gTvInputHalClassInfo;
+gTvStreamConfigClassInfoType gTvStreamConfigClassInfo;
+gTvStreamConfigBuilderClassInfoType gTvStreamConfigBuilderClassInfo;
+gTvInputHardwareInfoBuilderClassInfoType gTvInputHardwareInfoBuilderClassInfo;
namespace android {
-static struct {
- jmethodID deviceAvailable;
- jmethodID deviceUnavailable;
- jmethodID streamConfigsChanged;
- jmethodID firstFrameCaptured;
-} gTvInputHalClassInfo;
-
-static struct {
- jclass clazz;
-} gTvStreamConfigClassInfo;
-
-static struct {
- jclass clazz;
-
- jmethodID constructor;
- jmethodID streamId;
- jmethodID type;
- jmethodID maxWidth;
- jmethodID maxHeight;
- jmethodID generation;
- jmethodID build;
-} gTvStreamConfigBuilderClassInfo;
-
-static struct {
- jclass clazz;
-
- jmethodID constructor;
- jmethodID deviceId;
- jmethodID type;
- jmethodID hdmiPortId;
- jmethodID cableConnectionStatus;
- jmethodID audioType;
- jmethodID audioAddress;
- jmethodID build;
-} gTvInputHardwareInfoBuilderClassInfo;
-
-////////////////////////////////////////////////////////////////////////////////
-
-class BufferProducerThread : public Thread {
-public:
- BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream);
-
- virtual status_t readyToRun();
-
- void setSurface(const sp<Surface>& surface);
- void onCaptured(uint32_t seq, bool succeeded);
- void shutdown();
-
-private:
- Mutex mLock;
- Condition mCondition;
- sp<Surface> mSurface;
- tv_input_device_t* mDevice;
- int mDeviceId;
- tv_stream_t mStream;
- sp<ANativeWindowBuffer_t> mBuffer;
- enum {
- CAPTURING,
- CAPTURED,
- RELEASED,
- } mBufferState;
- uint32_t mSeq;
- bool mShutdown;
-
- virtual bool threadLoop();
-
- void setSurfaceLocked(const sp<Surface>& surface);
-};
-
-BufferProducerThread::BufferProducerThread(
- tv_input_device_t* device, int deviceId, const tv_stream_t* stream)
- : Thread(false),
- mDevice(device),
- mDeviceId(deviceId),
- mBuffer(NULL),
- mBufferState(RELEASED),
- mSeq(0u),
- mShutdown(false) {
- memcpy(&mStream, stream, sizeof(mStream));
-}
-
-status_t BufferProducerThread::readyToRun() {
- sp<ANativeWindow> anw(mSurface);
- status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage);
- if (err != NO_ERROR) {
- return err;
- }
- err = native_window_set_buffers_dimensions(
- anw.get(), mStream.buffer_producer.width, mStream.buffer_producer.height);
- if (err != NO_ERROR) {
- return err;
- }
- err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format);
- if (err != NO_ERROR) {
- return err;
- }
- return NO_ERROR;
-}
-
-void BufferProducerThread::setSurface(const sp<Surface>& surface) {
- Mutex::Autolock autoLock(&mLock);
- setSurfaceLocked(surface);
-}
-
-void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) {
- if (surface == mSurface) {
- return;
- }
-
- if (mBufferState == CAPTURING) {
- mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq);
- }
- while (mBufferState == CAPTURING) {
- status_t err = mCondition.waitRelative(mLock, s2ns(1));
- if (err != NO_ERROR) {
- ALOGE("error %d while wating for buffer state to change.", err);
- break;
- }
- }
- mBuffer.clear();
- mBufferState = RELEASED;
-
- mSurface = surface;
- mCondition.broadcast();
-}
-
-void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) {
- Mutex::Autolock autoLock(&mLock);
- if (seq != mSeq) {
- ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq);
- }
- if (mBufferState != CAPTURING) {
- ALOGW("mBufferState != CAPTURING : instead %d", mBufferState);
- }
- if (succeeded) {
- mBufferState = CAPTURED;
- } else {
- mBuffer.clear();
- mBufferState = RELEASED;
- }
- mCondition.broadcast();
-}
-
-void BufferProducerThread::shutdown() {
- Mutex::Autolock autoLock(&mLock);
- mShutdown = true;
- setSurfaceLocked(NULL);
- requestExitAndWait();
-}
-
-bool BufferProducerThread::threadLoop() {
- Mutex::Autolock autoLock(&mLock);
-
- status_t err = NO_ERROR;
- if (mSurface == NULL) {
- err = mCondition.waitRelative(mLock, s2ns(1));
- // It's OK to time out here.
- if (err != NO_ERROR && err != TIMED_OUT) {
- ALOGE("error %d while wating for non-null surface to be set", err);
- return false;
- }
- return true;
- }
- sp<ANativeWindow> anw(mSurface);
- while (mBufferState == CAPTURING) {
- err = mCondition.waitRelative(mLock, s2ns(1));
- if (err != NO_ERROR) {
- ALOGE("error %d while wating for buffer state to change.", err);
- return false;
- }
- }
- if (mBufferState == CAPTURED && anw != NULL) {
- err = anw->queueBuffer(anw.get(), mBuffer.get(), -1);
- if (err != NO_ERROR) {
- ALOGE("error %d while queueing buffer to surface", err);
- return false;
- }
- mBuffer.clear();
- mBufferState = RELEASED;
- }
- if (mBuffer == NULL && !mShutdown && anw != NULL) {
- ANativeWindowBuffer_t* buffer = NULL;
- err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer);
- if (err != NO_ERROR) {
- ALOGE("error %d while dequeueing buffer to surface", err);
- return false;
- }
- mBuffer = buffer;
- mBufferState = CAPTURING;
- mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id,
- buffer->handle, ++mSeq);
- }
-
- return true;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-class JTvInputHal {
-public:
- ~JTvInputHal();
-
- static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper);
-
- int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
- int removeStream(int deviceId, int streamId);
- const hidl_vec<TvStreamConfig> getStreamConfigs(int deviceId);
-
- void onDeviceAvailable(const TvInputDeviceInfo& info);
- void onDeviceUnavailable(int deviceId);
- void onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus);
- void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
-
-private:
- // Connection between a surface and a stream.
- class Connection {
- public:
- Connection() {}
-
- sp<Surface> mSurface;
- tv_stream_type_t mStreamType;
-
- // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE
- sp<NativeHandle> mSourceHandle;
- // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER
- sp<BufferProducerThread> mThread;
- };
-
- class NotifyHandler : public MessageHandler {
- public:
- NotifyHandler(JTvInputHal* hal, const TvInputEvent& event);
-
- virtual void handleMessage(const Message& message);
-
- private:
- TvInputEvent mEvent;
- JTvInputHal* mHal;
- };
-
- class TvInputCallback : public ITvInputCallback {
- public:
- explicit TvInputCallback(JTvInputHal* hal);
- Return<void> notify(const TvInputEvent& event) override;
- private:
- JTvInputHal* mHal;
- };
-
- JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput, const sp<Looper>& looper);
-
- Mutex mLock;
- Mutex mStreamLock;
- jweak mThiz;
- sp<Looper> mLooper;
-
- KeyedVector<int, KeyedVector<int, Connection> > mConnections;
-
- sp<ITvInput> mTvInput;
- sp<ITvInputCallback> mTvInputCallback;
-};
-
-JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, sp<ITvInput> tvInput,
- const sp<Looper>& looper) {
- mThiz = env->NewWeakGlobalRef(thiz);
- mTvInput = tvInput;
- mLooper = looper;
- mTvInputCallback = new TvInputCallback(this);
- mTvInput->setCallback(mTvInputCallback);
-}
-
-JTvInputHal::~JTvInputHal() {
- mTvInput->setCallback(nullptr);
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteWeakGlobalRef(mThiz);
- mThiz = NULL;
-}
-
-JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
- // TODO(b/31632518)
- sp<ITvInput> tvInput = ITvInput::getService();
- if (tvInput == nullptr) {
- ALOGE("Couldn't get tv.input service.");
- return nullptr;
- }
-
- return new JTvInputHal(env, thiz, tvInput, looper);
-}
-
-int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
- Mutex::Autolock autoLock(&mStreamLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- if (connections.indexOfKey(streamId) < 0) {
- connections.add(streamId, Connection());
- }
- Connection& connection = connections.editValueFor(streamId);
- if (connection.mSurface == surface) {
- // Nothing to do
- return NO_ERROR;
- }
- // Clear the surface in the connection.
- if (connection.mSurface != NULL) {
- if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
- if (Surface::isValid(connection.mSurface)) {
- connection.mSurface->setSidebandStream(NULL);
- }
- }
- connection.mSurface.clear();
- }
- if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
- // Need to configure stream
- Result result = Result::UNKNOWN;
- hidl_vec<TvStreamConfig> list;
- mTvInput->getStreamConfigurations(deviceId,
- [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
- result = res;
- if (res == Result::OK) {
- list = configs;
- }
- });
- if (result != Result::OK) {
- ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
- return UNKNOWN_ERROR;
- }
- int configIndex = -1;
- for (size_t i = 0; i < list.size(); ++i) {
- if (list[i].streamId == streamId) {
- configIndex = i;
- break;
- }
- }
- if (configIndex == -1) {
- ALOGE("Cannot find a config with given stream ID: %d", streamId);
- return BAD_VALUE;
- }
- connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
-
- result = Result::UNKNOWN;
- const native_handle_t* sidebandStream;
- mTvInput->openStream(deviceId, streamId,
- [&result, &sidebandStream](Result res, const native_handle_t* handle) {
- result = res;
- if (res == Result::OK) {
- if (handle) {
- sidebandStream = native_handle_clone(handle);
- } else {
- result = Result::UNKNOWN;
- }
- }
- });
- if (result != Result::OK) {
- ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
- result);
- return UNKNOWN_ERROR;
- }
- connection.mSourceHandle = NativeHandle::create((native_handle_t*)sidebandStream, true);
- }
- connection.mSurface = surface;
- if (connection.mSurface != nullptr) {
- connection.mSurface->setSidebandStream(connection.mSourceHandle);
- }
- return NO_ERROR;
-}
-
-int JTvInputHal::removeStream(int deviceId, int streamId) {
- Mutex::Autolock autoLock(&mStreamLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- if (connections.indexOfKey(streamId) < 0) {
- return BAD_VALUE;
- }
- Connection& connection = connections.editValueFor(streamId);
- if (connection.mSurface == NULL) {
- // Nothing to do
- return NO_ERROR;
- }
- if (Surface::isValid(connection.mSurface)) {
- connection.mSurface->setSidebandStream(NULL);
- }
- connection.mSurface.clear();
- if (connection.mThread != NULL) {
- connection.mThread->shutdown();
- connection.mThread.clear();
- }
- if (mTvInput->closeStream(deviceId, streamId) != Result::OK) {
- ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
- return BAD_VALUE;
- }
- if (connection.mSourceHandle != NULL) {
- connection.mSourceHandle.clear();
- }
- return NO_ERROR;
-}
-
-const hidl_vec<TvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
- Result result = Result::UNKNOWN;
- hidl_vec<TvStreamConfig> list;
- mTvInput->getStreamConfigurations(deviceId,
- [&result, &list](Result res, hidl_vec<TvStreamConfig> configs) {
- result = res;
- if (res == Result::OK) {
- list = configs;
- }
- });
- if (result != Result::OK) {
- ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, result);
- }
- return list;
-}
-
-void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) {
- {
- Mutex::Autolock autoLock(&mLock);
- mConnections.add(info.deviceId, KeyedVector<int, Connection>());
- }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
-
- jobject builder = env->NewObject(
- gTvInputHardwareInfoBuilderClassInfo.clazz,
- gTvInputHardwareInfoBuilderClassInfo.constructor);
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
- if (info.type == TvInputType::HDMI) {
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId);
- }
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus,
- info.cableConnectionStatus);
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType);
- if (info.audioType != AudioDevice::NONE) {
- uint8_t buffer[info.audioAddress.size() + 1];
- memcpy(buffer, info.audioAddress.data(), info.audioAddress.size());
- buffer[info.audioAddress.size()] = '\0';
- jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char *>(buffer));
- env->CallObjectMethod(
- builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress);
- env->DeleteLocalRef(audioAddress);
- }
-
- jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
-
- env->CallVoidMethod(
- mThiz,
- gTvInputHalClassInfo.deviceAvailable,
- infoObject);
-
- env->DeleteLocalRef(builder);
- env->DeleteLocalRef(infoObject);
-}
-
-void JTvInputHal::onDeviceUnavailable(int deviceId) {
- {
- Mutex::Autolock autoLock(&mLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- for (size_t i = 0; i < connections.size(); ++i) {
- removeStream(deviceId, connections.keyAt(i));
- }
- connections.clear();
- mConnections.removeItem(deviceId);
- }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(
- mThiz,
- gTvInputHalClassInfo.deviceUnavailable,
- deviceId);
-}
-
-void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
- {
- Mutex::Autolock autoLock(&mLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- for (size_t i = 0; i < connections.size(); ++i) {
- removeStream(deviceId, connections.keyAt(i));
- }
- connections.clear();
- }
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId,
- cableConnectionStatus);
-}
-
-void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
- sp<BufferProducerThread> thread;
- {
- Mutex::Autolock autoLock(&mLock);
- KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
- Connection& connection = connections.editValueFor(streamId);
- if (connection.mThread == NULL) {
- ALOGE("capture thread not existing.");
- return;
- }
- thread = connection.mThread;
- }
- thread->onCaptured(seq, succeeded);
- if (seq == 0) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(
- mThiz,
- gTvInputHalClassInfo.firstFrameCaptured,
- deviceId,
- streamId);
- }
-}
-
-JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEvent& event) {
- mHal = hal;
- mEvent = event;
-}
-
-void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
- switch (mEvent.type) {
- case TvInputEventType::DEVICE_AVAILABLE: {
- mHal->onDeviceAvailable(mEvent.deviceInfo);
- } break;
- case TvInputEventType::DEVICE_UNAVAILABLE: {
- mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
- } break;
- case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
- int cableConnectionStatus = static_cast<int>(mEvent.deviceInfo.cableConnectionStatus);
- mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus);
- } break;
- default:
- ALOGE("Unrecognizable event");
- }
-}
-
-JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
- mHal = hal;
-}
-
-Return<void> JTvInputHal::TvInputCallback::notify(const TvInputEvent& event) {
- mHal->mLooper->sendMessage(new NotifyHandler(mHal, event), static_cast<int>(event.type));
- return Void();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) {
sp<MessageQueue> messageQueue =
android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
@@ -617,7 +55,7 @@
static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz,
jlong ptr, jint deviceId, jint generation) {
JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
- const hidl_vec<TvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
+ const std::vector<AidlTvStreamConfig> configs = tvInputHal->getStreamConfigs(deviceId);
jobjectArray result = env->NewObjectArray(configs.size(), gTvStreamConfigClassInfo.clazz, NULL);
for (size_t i = 0; i < configs.size(); ++i) {
diff --git a/services/core/jni/tvinput/BufferProducerThread.cpp b/services/core/jni/tvinput/BufferProducerThread.cpp
new file mode 100644
index 0000000..f39dcee
--- /dev/null
+++ b/services/core/jni/tvinput/BufferProducerThread.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#include "BufferProducerThread.h"
+
+namespace android {
+
+BufferProducerThread::BufferProducerThread(tv_input_device_t* device, int deviceId,
+ const tv_stream_t* stream)
+ : Thread(false),
+ mDevice(device),
+ mDeviceId(deviceId),
+ mBuffer(NULL),
+ mBufferState(RELEASED),
+ mSeq(0u),
+ mShutdown(false) {
+ memcpy(&mStream, stream, sizeof(mStream));
+}
+
+status_t BufferProducerThread::readyToRun() {
+ sp<ANativeWindow> anw(mSurface);
+ status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = native_window_set_buffers_dimensions(anw.get(), mStream.buffer_producer.width,
+ mStream.buffer_producer.height);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ return NO_ERROR;
+}
+
+void BufferProducerThread::setSurface(const sp<Surface>& surface) {
+ Mutex::Autolock autoLock(&mLock);
+ setSurfaceLocked(surface);
+}
+
+void BufferProducerThread::setSurfaceLocked(const sp<Surface>& surface) {
+ if (surface == mSurface) {
+ return;
+ }
+
+ if (mBufferState == CAPTURING) {
+ mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq);
+ }
+ while (mBufferState == CAPTURING) {
+ status_t err = mCondition.waitRelative(mLock, s2ns(1));
+ if (err != NO_ERROR) {
+ ALOGE("error %d while wating for buffer state to change.", err);
+ break;
+ }
+ }
+ mBuffer.clear();
+ mBufferState = RELEASED;
+
+ mSurface = surface;
+ mCondition.broadcast();
+}
+
+void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) {
+ Mutex::Autolock autoLock(&mLock);
+ if (seq != mSeq) {
+ ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq);
+ }
+ if (mBufferState != CAPTURING) {
+ ALOGW("mBufferState != CAPTURING : instead %d", mBufferState);
+ }
+ if (succeeded) {
+ mBufferState = CAPTURED;
+ } else {
+ mBuffer.clear();
+ mBufferState = RELEASED;
+ }
+ mCondition.broadcast();
+}
+
+void BufferProducerThread::shutdown() {
+ Mutex::Autolock autoLock(&mLock);
+ mShutdown = true;
+ setSurfaceLocked(NULL);
+ requestExitAndWait();
+}
+
+bool BufferProducerThread::threadLoop() {
+ Mutex::Autolock autoLock(&mLock);
+
+ status_t err = NO_ERROR;
+ if (mSurface == NULL) {
+ err = mCondition.waitRelative(mLock, s2ns(1));
+ // It's OK to time out here.
+ if (err != NO_ERROR && err != TIMED_OUT) {
+ ALOGE("error %d while wating for non-null surface to be set", err);
+ return false;
+ }
+ return true;
+ }
+ sp<ANativeWindow> anw(mSurface);
+ while (mBufferState == CAPTURING) {
+ err = mCondition.waitRelative(mLock, s2ns(1));
+ if (err != NO_ERROR) {
+ ALOGE("error %d while wating for buffer state to change.", err);
+ return false;
+ }
+ }
+ if (mBufferState == CAPTURED && anw != NULL) {
+ err = anw->queueBuffer(anw.get(), mBuffer.get(), -1);
+ if (err != NO_ERROR) {
+ ALOGE("error %d while queueing buffer to surface", err);
+ return false;
+ }
+ mBuffer.clear();
+ mBufferState = RELEASED;
+ }
+ if (mBuffer == NULL && !mShutdown && anw != NULL) {
+ ANativeWindowBuffer_t* buffer = NULL;
+ err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer);
+ if (err != NO_ERROR) {
+ ALOGE("error %d while dequeueing buffer to surface", err);
+ return false;
+ }
+ mBuffer = buffer;
+ mBufferState = CAPTURING;
+ mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id, buffer->handle, ++mSeq);
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/BufferProducerThread.h b/services/core/jni/tvinput/BufferProducerThread.h
new file mode 100644
index 0000000..2e0fa91
--- /dev/null
+++ b/services/core/jni/tvinput/BufferProducerThread.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gui/Surface.h>
+#include <hardware/tv_input.h>
+#include <utils/Thread.h>
+
+namespace android {
+
+class BufferProducerThread : public Thread {
+public:
+ BufferProducerThread(tv_input_device_t* device, int deviceId, const tv_stream_t* stream);
+
+ virtual status_t readyToRun();
+
+ void setSurface(const sp<Surface>& surface);
+ void onCaptured(uint32_t seq, bool succeeded);
+ void shutdown();
+
+private:
+ Mutex mLock;
+ Condition mCondition;
+ sp<Surface> mSurface;
+ tv_input_device_t* mDevice;
+ int mDeviceId;
+ tv_stream_t mStream;
+ sp<ANativeWindowBuffer_t> mBuffer;
+ enum {
+ CAPTURING,
+ CAPTURED,
+ RELEASED,
+ } mBufferState;
+ uint32_t mSeq;
+ bool mShutdown;
+
+ virtual bool threadLoop();
+
+ void setSurfaceLocked(const sp<Surface>& surface);
+};
+
+} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
new file mode 100644
index 0000000..0f6ef03
--- /dev/null
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -0,0 +1,378 @@
+/*
+ * 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.
+ */
+
+#include "JTvInputHal.h"
+
+namespace android {
+
+JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
+ const sp<Looper>& looper) {
+ mThiz = env->NewWeakGlobalRef(thiz);
+ mTvInput = tvInput;
+ mLooper = looper;
+ mTvInputCallback = ::ndk::SharedRefBase::make<TvInputCallback>(this);
+ mTvInput->setCallback(mTvInputCallback);
+}
+
+JTvInputHal::~JTvInputHal() {
+ mTvInput->setCallback(nullptr);
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteWeakGlobalRef(mThiz);
+ mThiz = NULL;
+}
+
+JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper) {
+ sp<HidlITvInput> hidlITvInput = HidlITvInput::getService();
+ if (hidlITvInput != nullptr) {
+ ALOGD("tv.input service is HIDL.");
+ return new JTvInputHal(env, thiz,
+ std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(hidlITvInput)),
+ looper);
+ }
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
+ std::shared_ptr<AidlITvInput> aidlITvInput = AidlITvInput::fromBinder(binder);
+ if (aidlITvInput == nullptr) {
+ ALOGE("Couldn't get tv.input service.");
+ return nullptr;
+ }
+ return new JTvInputHal(env, thiz,
+ std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(aidlITvInput)),
+ looper);
+}
+
+int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface) {
+ Mutex::Autolock autoLock(&mStreamLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ if (connections.indexOfKey(streamId) < 0) {
+ connections.add(streamId, Connection());
+ }
+ Connection& connection = connections.editValueFor(streamId);
+ if (connection.mSurface == surface) {
+ // Nothing to do
+ return NO_ERROR;
+ }
+ // Clear the surface in the connection.
+ if (connection.mSurface != NULL) {
+ if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) {
+ if (Surface::isValid(connection.mSurface)) {
+ connection.mSurface->setSidebandStream(NULL);
+ }
+ }
+ connection.mSurface.clear();
+ }
+ if (connection.mSourceHandle == NULL && connection.mThread == NULL) {
+ // Need to configure stream
+ ::ndk::ScopedAStatus status;
+ std::vector<AidlTvStreamConfig> list;
+ status = mTvInput->getStreamConfigurations(deviceId, &list);
+ if (!status.isOk()) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId,
+ status.getServiceSpecificError());
+ return UNKNOWN_ERROR;
+ }
+ int configIndex = -1;
+ for (size_t i = 0; i < list.size(); ++i) {
+ if (list[i].streamId == streamId) {
+ configIndex = i;
+ break;
+ }
+ }
+ if (configIndex == -1) {
+ ALOGE("Cannot find a config with given stream ID: %d", streamId);
+ return BAD_VALUE;
+ }
+ connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE;
+
+ AidlNativeHandle sidebandStream;
+ status = mTvInput->openStream(deviceId, streamId, &sidebandStream);
+ if (!status.isOk()) {
+ ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId,
+ status.getServiceSpecificError());
+ return UNKNOWN_ERROR;
+ }
+ connection.mSourceHandle = NativeHandle::create(makeFromAidl(sidebandStream), true);
+ }
+ connection.mSurface = surface;
+ if (connection.mSurface != nullptr) {
+ connection.mSurface->setSidebandStream(connection.mSourceHandle);
+ }
+ return NO_ERROR;
+}
+
+int JTvInputHal::removeStream(int deviceId, int streamId) {
+ Mutex::Autolock autoLock(&mStreamLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ if (connections.indexOfKey(streamId) < 0) {
+ return BAD_VALUE;
+ }
+ Connection& connection = connections.editValueFor(streamId);
+ if (connection.mSurface == NULL) {
+ // Nothing to do
+ return NO_ERROR;
+ }
+ if (Surface::isValid(connection.mSurface)) {
+ connection.mSurface->setSidebandStream(NULL);
+ }
+ connection.mSurface.clear();
+ if (connection.mThread != NULL) {
+ connection.mThread->shutdown();
+ connection.mThread.clear();
+ }
+ if (!mTvInput->closeStream(deviceId, streamId).isOk()) {
+ ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId);
+ return BAD_VALUE;
+ }
+ if (connection.mSourceHandle != NULL) {
+ connection.mSourceHandle.clear();
+ }
+ return NO_ERROR;
+}
+
+const std::vector<AidlTvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
+ std::vector<AidlTvStreamConfig> list;
+ ::ndk::ScopedAStatus status = mTvInput->getStreamConfigurations(deviceId, &list);
+ if (!status.isOk()) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId,
+ status.getServiceSpecificError());
+ return std::vector<AidlTvStreamConfig>();
+ }
+ return list;
+}
+
+void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) {
+ {
+ Mutex::Autolock autoLock(&mLock);
+ mConnections.add(info.deviceId, KeyedVector<int, Connection>());
+ }
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jobject builder = env->NewObject(gTvInputHardwareInfoBuilderClassInfo.clazz,
+ gTvInputHardwareInfoBuilderClassInfo.constructor);
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId);
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type);
+ if (info.type == TvInputType::HDMI) {
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId,
+ info.portId);
+ }
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus,
+ info.cableConnectionStatus);
+ if (info.isHidl) {
+ hidlSetUpAudioInfo(env, builder, info);
+ } else {
+ AidlAudioDeviceType audioType = info.aidlAudioDevice.type.type;
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType, audioType);
+ if (audioType != AidlAudioDeviceType::NONE) {
+ std::stringstream ss;
+ switch (info.aidlAudioDevice.address.getTag()) {
+ case AidlAudioDeviceAddress::id:
+ ss << info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::id>();
+ break;
+ case AidlAudioDeviceAddress::mac: {
+ std::vector<uint8_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::mac>();
+ for (int i = 0; i < addrList.size(); i++) {
+ if (i != 0) {
+ ss << ':';
+ }
+ ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex
+ << static_cast<int32_t>(addrList[i]);
+ }
+ } break;
+ case AidlAudioDeviceAddress::ipv4: {
+ std::vector<uint8_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::ipv4>();
+ for (int i = 0; i < addrList.size(); i++) {
+ if (i != 0) {
+ ss << '.';
+ }
+ ss << static_cast<int32_t>(addrList[i]);
+ }
+ } break;
+ case AidlAudioDeviceAddress::ipv6: {
+ std::vector<int32_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::ipv6>();
+ for (int i = 0; i < addrList.size(); i++) {
+ if (i != 0) {
+ ss << ':';
+ }
+ ss << std::uppercase << std::setfill('0') << std::setw(4) << std::hex
+ << addrList[i];
+ }
+ } break;
+ case AidlAudioDeviceAddress::alsa: {
+ std::vector<int32_t> addrList =
+ info.aidlAudioDevice.address.get<AidlAudioDeviceAddress::alsa>();
+ ss << "card=" << addrList[0] << ";device=" << addrList[1];
+ } break;
+ }
+ std::string bufferStr = ss.str();
+ jstring audioAddress = env->NewStringUTF(bufferStr.c_str());
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+ audioAddress);
+ env->DeleteLocalRef(audioAddress);
+ }
+ }
+
+ jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build);
+
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.deviceAvailable, infoObject);
+
+ env->DeleteLocalRef(builder);
+ env->DeleteLocalRef(infoObject);
+}
+
+void JTvInputHal::onDeviceUnavailable(int deviceId) {
+ {
+ Mutex::Autolock autoLock(&mLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ for (size_t i = 0; i < connections.size(); ++i) {
+ removeStream(deviceId, connections.keyAt(i));
+ }
+ connections.clear();
+ mConnections.removeItem(deviceId);
+ }
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.deviceUnavailable, deviceId);
+}
+
+void JTvInputHal::onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus) {
+ {
+ Mutex::Autolock autoLock(&mLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ for (size_t i = 0; i < connections.size(); ++i) {
+ removeStream(deviceId, connections.keyAt(i));
+ }
+ connections.clear();
+ }
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId,
+ cableConnectionStatus);
+}
+
+void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) {
+ sp<BufferProducerThread> thread;
+ {
+ Mutex::Autolock autoLock(&mLock);
+ KeyedVector<int, Connection>& connections = mConnections.editValueFor(deviceId);
+ Connection& connection = connections.editValueFor(streamId);
+ if (connection.mThread == NULL) {
+ ALOGE("capture thread not existing.");
+ return;
+ }
+ thread = connection.mThread;
+ }
+ thread->onCaptured(seq, succeeded);
+ if (seq == 0) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mThiz, gTvInputHalClassInfo.firstFrameCaptured, deviceId, streamId);
+ }
+}
+
+JTvInputHal::TvInputDeviceInfoWrapper
+JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper(
+ const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo) {
+ TvInputDeviceInfoWrapper deviceInfo;
+ deviceInfo.isHidl = false;
+ deviceInfo.deviceId = aidlTvInputDeviceInfo.deviceId;
+ deviceInfo.type = aidlTvInputDeviceInfo.type;
+ deviceInfo.portId = aidlTvInputDeviceInfo.portId;
+ deviceInfo.cableConnectionStatus = aidlTvInputDeviceInfo.cableConnectionStatus;
+ deviceInfo.aidlAudioDevice = aidlTvInputDeviceInfo.audioDevice;
+ return deviceInfo;
+}
+
+JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper(
+ const AidlTvInputEvent& aidlTvInputEvent) {
+ TvInputEventWrapper event;
+ event.type = aidlTvInputEvent.type;
+ event.deviceInfo =
+ TvInputDeviceInfoWrapper::createDeviceInfoWrapper(aidlTvInputEvent.deviceInfo);
+ return event;
+}
+
+JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event) {
+ mHal = hal;
+ mEvent = event;
+}
+
+void JTvInputHal::NotifyHandler::handleMessage(const Message& message) {
+ switch (mEvent.type) {
+ case TvInputEventType::DEVICE_AVAILABLE: {
+ mHal->onDeviceAvailable(mEvent.deviceInfo);
+ } break;
+ case TvInputEventType::DEVICE_UNAVAILABLE: {
+ mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId);
+ } break;
+ case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: {
+ int cableConnectionStatus = static_cast<int>(mEvent.deviceInfo.cableConnectionStatus);
+ mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus);
+ } break;
+ default:
+ ALOGE("Unrecognizable event");
+ }
+}
+
+JTvInputHal::TvInputCallback::TvInputCallback(JTvInputHal* hal) {
+ mHal = hal;
+}
+
+::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notify(const AidlTvInputEvent& event) {
+ mHal->mLooper->sendMessage(new NotifyHandler(mHal,
+ TvInputEventWrapper::createEventWrapper(event)),
+ static_cast<int>(event.type));
+ return ::ndk::ScopedAStatus::ok();
+}
+
+JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
+ : mIsHidl(false), mAidlTvInput(aidlTvInput) {}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setCallback(
+ const std::shared_ptr<TvInputCallback>& in_callback) {
+ if (mIsHidl) {
+ return hidlSetCallback(in_callback);
+ } else {
+ return mAidlTvInput->setCallback(in_callback);
+ }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getStreamConfigurations(
+ int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return) {
+ if (mIsHidl) {
+ return hidlGetStreamConfigurations(in_deviceId, _aidl_return);
+ } else {
+ return mAidlTvInput->getStreamConfigurations(in_deviceId, _aidl_return);
+ }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::openStream(int32_t in_deviceId,
+ int32_t in_streamId,
+ AidlNativeHandle* _aidl_return) {
+ if (mIsHidl) {
+ return hidlOpenStream(in_deviceId, in_streamId, _aidl_return);
+ } else {
+ return mAidlTvInput->openStream(in_deviceId, in_streamId, _aidl_return);
+ }
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::closeStream(int32_t in_deviceId,
+ int32_t in_streamId) {
+ if (mIsHidl) {
+ return hidlCloseStream(in_deviceId, in_streamId);
+ } else {
+ return mAidlTvInput->closeStream(in_deviceId, in_streamId);
+ }
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
new file mode 100644
index 0000000..2697294
--- /dev/null
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#define TV_INPUT_AIDL_SERVICE_NAME "android.hardware.tv.input.ITvInput/default"
+
+#include <aidl/android/hardware/tv/input/BnTvInputCallback.h>
+#include <aidl/android/hardware/tv/input/CableConnectionStatus.h>
+#include <aidl/android/hardware/tv/input/ITvInput.h>
+#include <aidl/android/media/audio/common/AudioDevice.h>
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <nativehelper/JNIHelp.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/NativeHandle.h>
+
+#include <iomanip>
+
+#include "BufferProducerThread.h"
+#include "TvInputHal_hidl.h"
+#include "android_os_MessageQueue.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "tvinput/jstruct.h"
+
+using ::aidl::android::hardware::tv::input::BnTvInputCallback;
+using ::aidl::android::hardware::tv::input::CableConnectionStatus;
+using ::aidl::android::hardware::tv::input::TvInputEventType;
+using ::aidl::android::hardware::tv::input::TvInputType;
+
+using AidlAudioDevice = ::aidl::android::media::audio::common::AudioDevice;
+using AidlAudioDeviceAddress = ::aidl::android::media::audio::common::AudioDeviceAddress;
+using AidlAudioDeviceType = ::aidl::android::media::audio::common::AudioDeviceType;
+using AidlITvInput = ::aidl::android::hardware::tv::input::ITvInput;
+using AidlNativeHandle = ::aidl::android::hardware::common::NativeHandle;
+using AidlTvInputDeviceInfo = ::aidl::android::hardware::tv::input::TvInputDeviceInfo;
+using AidlTvInputEvent = ::aidl::android::hardware::tv::input::TvInputEvent;
+using AidlTvStreamConfig = ::aidl::android::hardware::tv::input::TvStreamConfig;
+
+extern gTvInputHalClassInfoType gTvInputHalClassInfo;
+extern gTvStreamConfigClassInfoType gTvStreamConfigClassInfo;
+extern gTvStreamConfigBuilderClassInfoType gTvStreamConfigBuilderClassInfo;
+extern gTvInputHardwareInfoBuilderClassInfoType gTvInputHardwareInfoBuilderClassInfo;
+
+namespace android {
+
+class JTvInputHal {
+public:
+ ~JTvInputHal();
+
+ static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp<Looper>& looper);
+
+ int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
+ int removeStream(int deviceId, int streamId);
+ const std::vector<AidlTvStreamConfig> getStreamConfigs(int deviceId);
+
+private:
+ // Connection between a surface and a stream.
+ class Connection {
+ public:
+ Connection() {}
+
+ sp<Surface> mSurface;
+ tv_stream_type_t mStreamType;
+
+ // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE
+ sp<NativeHandle> mSourceHandle;
+ // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER
+ sp<BufferProducerThread> mThread;
+ };
+
+ class TvInputDeviceInfoWrapper {
+ public:
+ TvInputDeviceInfoWrapper() {}
+
+ static TvInputDeviceInfoWrapper createDeviceInfoWrapper(
+ const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo);
+ static TvInputDeviceInfoWrapper createDeviceInfoWrapper(
+ const HidlTvInputDeviceInfo& hidlTvInputDeviceInfo);
+
+ bool isHidl;
+ int deviceId;
+ TvInputType type;
+ int portId;
+ CableConnectionStatus cableConnectionStatus;
+ AidlAudioDevice aidlAudioDevice;
+ HidlAudioDevice hidlAudioType;
+ ::android::hardware::hidl_array<uint8_t, 32> hidlAudioAddress;
+ };
+
+ class TvInputEventWrapper {
+ public:
+ TvInputEventWrapper() {}
+
+ static TvInputEventWrapper createEventWrapper(const AidlTvInputEvent& aidlTvInputEvent);
+ static TvInputEventWrapper createEventWrapper(const HidlTvInputEvent& hidlTvInputEvent);
+
+ TvInputEventType type;
+ TvInputDeviceInfoWrapper deviceInfo;
+ };
+
+ class NotifyHandler : public MessageHandler {
+ public:
+ NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event);
+
+ void handleMessage(const Message& message) override;
+
+ private:
+ TvInputEventWrapper mEvent;
+ JTvInputHal* mHal;
+ };
+
+ class TvInputCallback : public HidlITvInputCallback, public BnTvInputCallback {
+ public:
+ explicit TvInputCallback(JTvInputHal* hal);
+ ::ndk::ScopedAStatus notify(const AidlTvInputEvent& event) override;
+ Return<void> notify(const HidlTvInputEvent& event) override;
+
+ private:
+ JTvInputHal* mHal;
+ };
+
+ class ITvInputWrapper {
+ public:
+ ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput);
+ ITvInputWrapper(sp<HidlITvInput>& hidlTvInput);
+
+ ::ndk::ScopedAStatus setCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+ ::ndk::ScopedAStatus getStreamConfigurations(int32_t in_deviceId,
+ std::vector<AidlTvStreamConfig>* _aidl_return);
+ ::ndk::ScopedAStatus openStream(int32_t in_deviceId, int32_t in_streamId,
+ AidlNativeHandle* _aidl_return);
+ ::ndk::ScopedAStatus closeStream(int32_t in_deviceId, int32_t in_streamId);
+
+ private:
+ ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
+ ::ndk::ScopedAStatus hidlGetStreamConfigurations(
+ int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return);
+ ::ndk::ScopedAStatus hidlOpenStream(int32_t in_deviceId, int32_t in_streamId,
+ AidlNativeHandle* _aidl_return);
+ ::ndk::ScopedAStatus hidlCloseStream(int32_t in_deviceId, int32_t in_streamId);
+
+ bool mIsHidl;
+ sp<HidlITvInput> mHidlTvInput;
+ std::shared_ptr<AidlITvInput> mAidlTvInput;
+ };
+
+ JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr<ITvInputWrapper> tvInput,
+ const sp<Looper>& looper);
+
+ void hidlSetUpAudioInfo(JNIEnv* env, jobject& builder, const TvInputDeviceInfoWrapper& info);
+ void onDeviceAvailable(const TvInputDeviceInfoWrapper& info);
+ void onDeviceUnavailable(int deviceId);
+ void onStreamConfigurationsChanged(int deviceId, int cableConnectionStatus);
+ void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded);
+
+ Mutex mLock;
+ Mutex mStreamLock;
+ jweak mThiz;
+ sp<Looper> mLooper;
+
+ KeyedVector<int, KeyedVector<int, Connection> > mConnections;
+
+ std::shared_ptr<ITvInputWrapper> mTvInput;
+ std::shared_ptr<TvInputCallback> mTvInputCallback;
+};
+
+} // namespace android
diff --git a/services/core/jni/tvinput/OWNERS b/services/core/jni/tvinput/OWNERS
new file mode 100644
index 0000000..adc5827
--- /dev/null
+++ b/services/core/jni/tvinput/OWNERS
@@ -0,0 +1,3 @@
+include /media/java/android/media/tv/OWNERS
+
+yixiaoluo@google.com
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.cpp b/services/core/jni/tvinput/TvInputHal_hidl.cpp
new file mode 100644
index 0000000..37cf844
--- /dev/null
+++ b/services/core/jni/tvinput/TvInputHal_hidl.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#include "JTvInputHal.h"
+
+// Implement all HIDL related functions here.
+
+namespace android {
+
+void JTvInputHal::hidlSetUpAudioInfo(JNIEnv* env, jobject& builder,
+ const TvInputDeviceInfoWrapper& info) {
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType,
+ info.hidlAudioType);
+ if (info.hidlAudioType != HidlAudioDevice::NONE) {
+ uint8_t buffer[info.hidlAudioAddress.size() + 1];
+ memcpy(buffer, info.hidlAudioAddress.data(), info.hidlAudioAddress.size());
+ buffer[info.hidlAudioAddress.size()] = '\0';
+ jstring audioAddress = env->NewStringUTF(reinterpret_cast<const char*>(buffer));
+ env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress,
+ audioAddress);
+ env->DeleteLocalRef(audioAddress);
+ }
+}
+
+JTvInputHal::TvInputDeviceInfoWrapper
+JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper(
+ const HidlTvInputDeviceInfo& hidlTvInputDeviceInfo) {
+ TvInputDeviceInfoWrapper deviceInfo;
+ deviceInfo.isHidl = true;
+ deviceInfo.deviceId = hidlTvInputDeviceInfo.deviceId;
+ deviceInfo.type = TvInputType(static_cast<int32_t>(hidlTvInputDeviceInfo.type));
+ deviceInfo.portId = hidlTvInputDeviceInfo.portId;
+ deviceInfo.cableConnectionStatus = CableConnectionStatus(
+ static_cast<int32_t>(hidlTvInputDeviceInfo.cableConnectionStatus));
+ deviceInfo.hidlAudioType = hidlTvInputDeviceInfo.audioType;
+ deviceInfo.hidlAudioAddress = hidlTvInputDeviceInfo.audioAddress;
+ return deviceInfo;
+}
+
+JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper(
+ const HidlTvInputEvent& hidlTvInputEvent) {
+ TvInputEventWrapper event;
+ event.type = TvInputEventType(static_cast<int32_t>(hidlTvInputEvent.type));
+ event.deviceInfo =
+ TvInputDeviceInfoWrapper::createDeviceInfoWrapper(hidlTvInputEvent.deviceInfo);
+ return event;
+}
+
+Return<void> JTvInputHal::TvInputCallback::notify(const HidlTvInputEvent& event) {
+ mHal->mLooper->sendMessage(new NotifyHandler(mHal,
+ TvInputEventWrapper::createEventWrapper(event)),
+ static_cast<int>(event.type));
+ return Void();
+}
+
+JTvInputHal::ITvInputWrapper::ITvInputWrapper(sp<HidlITvInput>& hidlTvInput)
+ : mIsHidl(true), mHidlTvInput(hidlTvInput) {}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlSetCallback(
+ const std::shared_ptr<TvInputCallback>& in_callback) {
+ mHidlTvInput->setCallback(in_callback == nullptr ? nullptr
+ : sp<TvInputCallback>(in_callback.get()));
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlGetStreamConfigurations(
+ int32_t in_deviceId, std::vector<AidlTvStreamConfig>* _aidl_return) {
+ Result result = Result::UNKNOWN;
+ hidl_vec<HidlTvStreamConfig> list;
+ mHidlTvInput->getStreamConfigurations(in_deviceId,
+ [&result, &list](Result res,
+ hidl_vec<HidlTvStreamConfig> configs) {
+ result = res;
+ if (res == Result::OK) {
+ list = configs;
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't get stream configs for device id:%d result:%d", in_deviceId, result);
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+ }
+ for (size_t i = 0; i < list.size(); ++i) {
+ AidlTvStreamConfig config;
+ config.streamId = list[i].streamId;
+ config.maxVideoHeight = list[i].maxVideoHeight;
+ config.maxVideoWidth = list[i].maxVideoWidth;
+ _aidl_return->push_back(config);
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlOpenStream(int32_t in_deviceId,
+ int32_t in_streamId,
+ AidlNativeHandle* _aidl_return) {
+ Result result = Result::UNKNOWN;
+ native_handle_t* sidebandStream;
+ mHidlTvInput->openStream(in_deviceId, in_streamId,
+ [&result, &sidebandStream](Result res, const native_handle_t* handle) {
+ result = res;
+ if (res == Result::OK) {
+ if (handle) {
+ sidebandStream = native_handle_clone(handle);
+ }
+ }
+ });
+ if (result != Result::OK) {
+ ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", in_deviceId, in_streamId,
+ result);
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+ }
+ *_aidl_return = makeToAidl(sidebandStream);
+ native_handle_delete(sidebandStream);
+ return ::ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::hidlCloseStream(int32_t in_deviceId,
+ int32_t in_streamId) {
+ Result result = mHidlTvInput->closeStream(in_deviceId, in_streamId);
+ if (result != Result::OK) {
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(static_cast<int32_t>(result));
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+} // namespace android
diff --git a/services/core/jni/tvinput/TvInputHal_hidl.h b/services/core/jni/tvinput/TvInputHal_hidl.h
new file mode 100644
index 0000000..948f86b
--- /dev/null
+++ b/services/core/jni/tvinput/TvInputHal_hidl.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+// Include all HIDL related files/names here.
+
+#include <android/hardware/tv/input/1.0/ITvInput.h>
+#include <android/hardware/tv/input/1.0/ITvInputCallback.h>
+#include <android/hardware/tv/input/1.0/types.h>
+
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::tv::input::V1_0::Result;
+
+using HidlAudioDevice = ::android::hardware::audio::common::V2_0::AudioDevice;
+using HidlITvInput = ::android::hardware::tv::input::V1_0::ITvInput;
+using HidlITvInputCallback = ::android::hardware::tv::input::V1_0::ITvInputCallback;
+using HidlTvInputDeviceInfo = ::android::hardware::tv::input::V1_0::TvInputDeviceInfo;
+using HidlTvInputEvent = ::android::hardware::tv::input::V1_0::TvInputEvent;
+using HidlTvStreamConfig = ::android::hardware::tv::input::V1_0::TvStreamConfig;
diff --git a/services/core/jni/tvinput/jstruct.h b/services/core/jni/tvinput/jstruct.h
new file mode 100644
index 0000000..0a4a64d
--- /dev/null
+++ b/services/core/jni/tvinput/jstruct.h
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "jni.h"
+
+typedef struct {
+ jmethodID deviceAvailable;
+ jmethodID deviceUnavailable;
+ jmethodID streamConfigsChanged;
+ jmethodID firstFrameCaptured;
+} gTvInputHalClassInfoType;
+
+typedef struct {
+ jclass clazz;
+} gTvStreamConfigClassInfoType;
+
+typedef struct {
+ jclass clazz;
+
+ jmethodID constructor;
+ jmethodID streamId;
+ jmethodID type;
+ jmethodID maxWidth;
+ jmethodID maxHeight;
+ jmethodID generation;
+ jmethodID build;
+} gTvStreamConfigBuilderClassInfoType;
+
+typedef struct {
+ jclass clazz;
+
+ jmethodID constructor;
+ jmethodID deviceId;
+ jmethodID type;
+ jmethodID hdmiPortId;
+ jmethodID cableConnectionStatus;
+ jmethodID audioType;
+ jmethodID audioAddress;
+ jmethodID build;
+} gTvInputHardwareInfoBuilderClassInfoType;
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index e14139a..842d97a 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -50,6 +50,7 @@
<xs:complexType name="display">
<xs:sequence>
<xs:element name="address" type="xs:nonNegativeInteger"/>
+ <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="enabled" type="xs:boolean" use="optional" />
<xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" />
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index f391575..55f866c 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -4,11 +4,13 @@
public class Display {
ctor public Display();
method public java.math.BigInteger getAddress();
+ method public String getPosition();
method public boolean isDefaultDisplay();
method public boolean isEnabled();
method public void setAddress(java.math.BigInteger);
method public void setDefaultDisplay(boolean);
method public void setEnabled(boolean);
+ method public void setPosition(String);
}
public class Layout {
diff --git a/services/permission/Android.bp b/services/permission/Android.bp
index b03f17b..dc9b558 100644
--- a/services/permission/Android.bp
+++ b/services/permission/Android.bp
@@ -29,6 +29,8 @@
],
static_libs: [
"kotlin-stdlib",
+ // Adds reflection-less suppressed exceptions and AutoCloseable.use().
+ "kotlin-stdlib-jdk7",
],
jarjar_rules: "jarjar-rules.txt",
kotlincflags: [
diff --git a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
index c6ccc0f..7b96d42 100644
--- a/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt
@@ -40,46 +40,53 @@
}
fun getDecision(subject: AccessUri, `object`: AccessUri): Int =
- policy.getDecision(subject, `object`, state)
+ getState {
+ with(policy) { getDecision(subject, `object`) }
+ }
fun setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
- mutateState { oldState, newState ->
- policy.setDecision(subject, `object`, decision, oldState, newState)
+ mutateState {
+ with(policy) { setDecision(subject, `object`, decision) }
}
}
fun onUserAdded(userId: Int) {
- mutateState { oldState, newState ->
- policy.onUserAdded(userId, oldState, newState)
+ mutateState {
+ with(policy) { onUserAdded(userId) }
}
}
fun onUserRemoved(userId: Int) {
- mutateState { oldState, newState ->
- policy.onUserRemoved(userId, oldState, newState)
+ mutateState {
+ with(policy) { onUserRemoved(userId) }
}
}
fun onPackageAdded(packageState: PackageState) {
- mutateState { oldState, newState ->
- policy.onPackageAdded(packageState, oldState, newState)
+ mutateState {
+ with(policy) { onPackageAdded(packageState) }
}
}
fun onPackageRemoved(packageState: PackageState) {
- mutateState { oldState, newState ->
- policy.onPackageRemoved(packageState, oldState, newState)
+ mutateState {
+ with(policy) { onPackageRemoved(packageState) }
}
}
- // TODO: Replace (oldState, newState) with Kotlin context receiver once it's stabilized.
- private inline fun mutateState(action: (oldState: AccessState, newState: AccessState) -> Unit) {
+ internal inline fun <T> getState(action: GetStateScope.() -> T): T =
+ GetStateScope(state).action()
+
+ internal inline fun mutateState(action: MutateStateScope.() -> Unit) {
synchronized(stateLock) {
val oldState = state
val newState = oldState.copy()
- action(oldState, newState)
+ MutateStateScope(oldState, newState).action()
persistence.write(newState)
state = newState
}
}
+
+ internal fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
+ policy.getSchemePolicy(subjectScheme, objectScheme)
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
index 0efc1bd..022f09a 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPersistence.kt
@@ -44,13 +44,13 @@
systemFile.parse {
// This is the canonical way to call an extension function in a different class.
// TODO(b/259469752): Use context receiver for this when it becomes stable.
- with(policy) { this@parse.parseSystemState(systemState) }
+ with(policy) { parseSystemState(systemState) }
}
}
private fun readUserState(userId: Int, userState: UserState) {
getUserFile(userId).parse {
- with(policy) { this@parse.parseUserState(userId, userState) }
+ with(policy) { parseUserState(userId, userState) }
}
}
@@ -82,13 +82,13 @@
private fun writeSystemState(systemState: SystemState) {
systemFile.serialize {
- with(policy) { this@serialize.serializeSystemState(systemState) }
+ with(policy) { serializeSystemState(systemState) }
}
}
private fun writeUserState(userId: Int, userState: UserState) {
getUserFile(userId).serialize {
- with(policy) { this@serialize.serializeUserState(userId, userState) }
+ with(policy) { serializeUserState(userId, userState) }
}
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 4e53ce0..e9741c6 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -41,38 +41,35 @@
}
)
- fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int =
- getSchemePolicy(subject, `object`).getDecision(subject, `object`, state)
-
- fun setDecision(
- subject: AccessUri,
- `object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
- ) {
- getSchemePolicy(subject, `object`)
- .setDecision(subject, `object`, decision, oldState, newState)
- }
-
- private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
- checkNotNull(schemePolicies[subject.scheme]?.get(`object`.scheme)) {
- "Scheme policy for subject=$subject object=$`object` does not exist"
+ fun getSchemePolicy(subjectScheme: String, objectScheme: String): SchemePolicy =
+ checkNotNull(schemePolicies[subjectScheme]?.get(objectScheme)) {
+ "Scheme policy for $subjectScheme and $objectScheme does not exist"
}
- fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
+ fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int =
+ with(getSchemePolicy(subject, `object`)){ getDecision(subject, `object`) }
+
+ fun MutateStateScope.setDecision(subject: AccessUri, `object`: AccessUri, decision: Int) {
+ with(getSchemePolicy(subject, `object`)) { setDecision(subject, `object`, decision) }
+ }
+
+ fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.userIds += userId
newState.userStates[userId] = UserState()
- forEachSchemePolicy { it.onUserAdded(userId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onUserAdded(userId) }
+ }
}
- fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onUserRemoved(userId: Int) {
newState.systemState.userIds -= userId
newState.userStates -= userId
- forEachSchemePolicy { it.onUserRemoved(userId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onUserRemoved(userId) }
+ }
}
- fun onPackageAdded(packageState: PackageState, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onPackageAdded(packageState: PackageState) {
var isAppIdAdded = false
newState.systemState.apply {
packageStates[packageState.packageName] = packageState
@@ -82,12 +79,16 @@
}.add(packageState.packageName)
}
if (isAppIdAdded) {
- forEachSchemePolicy { it.onAppIdAdded(packageState.appId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onAppIdAdded(packageState.appId) }
+ }
}
- forEachSchemePolicy { it.onPackageAdded(packageState, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onPackageAdded(packageState) }
+ }
}
- fun onPackageRemoved(packageState: PackageState, oldState: AccessState, newState: AccessState) {
+ fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
var isAppIdRemoved = false
newState.systemState.apply {
packageStates -= packageState.packageName
@@ -101,9 +102,13 @@
}
}
}
- forEachSchemePolicy { it.onPackageRemoved(packageState, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onPackageRemoved(packageState) }
+ }
if (isAppIdRemoved) {
- forEachSchemePolicy { it.onAppIdRemoved(packageState.appId, oldState, newState) }
+ forEachSchemePolicy {
+ with(it) { onAppIdRemoved(packageState.appId) }
+ }
}
}
@@ -113,7 +118,7 @@
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
- with(it) { this@parseSystemState.parseSystemState(systemState) }
+ with(it) { parseSystemState(systemState) }
}
}
}
@@ -125,7 +130,7 @@
fun BinaryXmlSerializer.serializeSystemState(systemState: SystemState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
- with(it) { this@serializeSystemState.serializeSystemState(systemState) }
+ with(it) { serializeSystemState(systemState) }
}
}
}
@@ -136,7 +141,7 @@
TAG_ACCESS -> {
forEachTag {
forEachSchemePolicy {
- with(it) { this@parseUserState.parseUserState(userId, userState) }
+ with(it) { parseUserState(userId, userState) }
}
}
}
@@ -153,11 +158,14 @@
fun BinaryXmlSerializer.serializeUserState(userId: Int, userState: UserState) {
tag(TAG_ACCESS) {
forEachSchemePolicy {
- with(it) { this@serializeUserState.serializeUserState(userId, userState) }
+ with(it) { serializeUserState(userId, userState) }
}
}
}
+ private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
+ getSchemePolicy(subject.scheme, `object`.scheme)
+
private inline fun forEachSchemePolicy(action: (SchemePolicy) -> Unit) {
schemePolicies.forEachValueIndexed { _, objectSchemePolicies ->
objectSchemePolicies.forEachValueIndexed { _, schemePolicy ->
@@ -182,14 +190,12 @@
abstract val objectScheme: String
- abstract fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int
+ abstract fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int
- abstract fun setDecision(
+ abstract fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
+ decision: Int
)
fun addOnDecisionChangedListener(listener: OnDecisionChangedListener) {
@@ -216,25 +222,17 @@
}
}
- open fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onUserAdded(userId: Int) {}
- open fun onUserRemoved(userId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onUserRemoved(userId: Int) {}
- open fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onAppIdAdded(appId: Int) {}
- open fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {}
+ open fun MutateStateScope.onAppIdRemoved(appId: Int) {}
- open fun onPackageAdded(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {}
+ open fun MutateStateScope.onPackageAdded(packageState: PackageState) {}
- open fun onPackageRemoved(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {}
+ open fun MutateStateScope.onPackageRemoved(packageState: PackageState) {}
open fun BinaryXmlPullParser.parseSystemState(systemState: SystemState) {}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index f496dbd..cf8f383 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -124,3 +124,12 @@
}
}
}
+
+class GetStateScope(
+ val state: AccessState
+)
+
+class MutateStateScope(
+ val oldState: AccessState,
+ val newState: AccessState
+)
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
new file mode 100644
index 0000000..a565feb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.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.server.permission.access.appop
+
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import android.util.SparseIntArray
+import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.appop.OnOpModeChangedListener
+import com.android.server.permission.access.AccessCheckingService
+import java.io.PrintWriter
+
+class AppOpsCheckingServiceCompatImpl(
+ private val accessCheckingService: AccessCheckingService
+) : AppOpsCheckingServiceInterface {
+ override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun getUidMode(uid: Int, op: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removePackage(packageName: String, userId: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeUid(uid: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun areUidModesDefault(uid: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun clearAllModes() {
+ TODO("Not yet implemented")
+ }
+
+ override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun startWatchingPackageModeChanged(
+ changedListener: OnOpModeChangedListener,
+ packageName: String
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeListener(changedListener: OnOpModeChangedListener) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPackageModeChangedListeners(
+ packageName: String
+ ): ArraySet<OnOpModeChangedListener> {
+ TODO("Not yet implemented")
+ }
+
+ override fun notifyWatchersOfChange(op: Int, uid: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun notifyOpChanged(
+ changedListener: OnOpModeChangedListener,
+ op: Int,
+ uid: Int,
+ packageName: String?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun notifyOpChangedForAllPkgsInUid(
+ op: Int,
+ uid: Int,
+ onlyForeground: Boolean,
+ callbackToIgnore: OnOpModeChangedListener?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun evalForegroundUidOps(
+ uid: Int,
+ foregroundOps: SparseBooleanArray?
+ ): SparseBooleanArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun evalForegroundPackageOps(
+ packageName: String,
+ foregroundOps: SparseBooleanArray?,
+ userId: Int
+ ): SparseBooleanArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun dumpListeners(
+ dumpOp: Int,
+ dumpUid: Int,
+ dumpPackage: String?,
+ printWriter: PrintWriter
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
index 0b052f9..a1a5e2d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/BaseAppOpPolicy.kt
@@ -19,44 +19,43 @@
import android.app.AppOpsManager
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
abstract class BaseAppOpPolicy(private val persistence: BaseAppOpPersistence) : SchemePolicy() {
- override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int {
+ override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
`object` as AppOpUri
- return getModes(subject, state)
+ return getModes(subject)
.getWithDefault(`object`.appOpName, opToDefaultMode(`object`.appOpName))
}
- override fun setDecision(
+ override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
+ decision: Int
) {
`object` as AppOpUri
- val modes = getOrCreateModes(subject, newState)
+ val modes = getOrCreateModes(subject)
val oldMode = modes.putWithDefault(`object`.appOpName, decision,
opToDefaultMode(`object`.appOpName))
if (modes.isEmpty()) {
- removeModes(subject, newState)
+ removeModes(subject)
}
if (oldMode != decision) {
notifyOnDecisionChangedListeners(subject, `object`, oldMode, decision)
}
}
- abstract fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>?
+ abstract fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>?
- abstract fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>
+ abstract fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int>
- abstract fun removeModes(subject: AccessUri, state: AccessState)
+ abstract fun MutateStateScope.removeModes(subject: AccessUri)
// TODO need to check that [AppOpsManager.getSystemAlertWindowDefault] works; likely no issue
// since running in system process.
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 7c3c14c..966489f 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -16,9 +16,10 @@
package com.android.server.permission.access.appop
-import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PackageUri
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -31,27 +32,23 @@
override val objectScheme: String
get() = AppOpUri.SCHEME
- override fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>? {
+ override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
subject as PackageUri
return state.userStates[subject.userId]?.packageAppOpModes?.get(subject.packageName)
}
- override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int> {
+ override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
subject as PackageUri
- return state.userStates.getOrPut(subject.userId) { UserState() }
+ return newState.userStates.getOrPut(subject.userId) { UserState() }
.packageAppOpModes.getOrPut(subject.packageName) { IndexedMap() }
}
- override fun removeModes(subject: AccessUri, state: AccessState) {
+ override fun MutateStateScope.removeModes(subject: AccessUri) {
subject as PackageUri
- state.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
+ newState.userStates[subject.userId]?.packageAppOpModes?.remove(subject.packageName)
}
- override fun onPackageRemoved(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {
+ override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.packageAppOpModes -= packageState.packageName
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
index 26d0114..862db8f 100644
--- a/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/UidAppOpPolicy.kt
@@ -16,9 +16,10 @@
package com.android.server.permission.access.appop
-import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.UidUri
import com.android.server.permission.access.UserState
import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
@@ -30,23 +31,23 @@
override val objectScheme: String
get() = AppOpUri.SCHEME
- override fun getModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int>? {
+ override fun GetStateScope.getModes(subject: AccessUri): IndexedMap<String, Int>? {
subject as UidUri
return state.userStates[subject.userId]?.uidAppOpModes?.get(subject.appId)
}
- override fun getOrCreateModes(subject: AccessUri, state: AccessState): IndexedMap<String, Int> {
+ override fun MutateStateScope.getOrCreateModes(subject: AccessUri): IndexedMap<String, Int> {
subject as UidUri
- return state.userStates.getOrPut(subject.userId) { UserState() }
+ return newState.userStates.getOrPut(subject.userId) { UserState() }
.uidAppOpModes.getOrPut(subject.appId) { IndexedMap() }
}
- override fun removeModes(subject: AccessUri, state: AccessState) {
+ override fun MutateStateScope.removeModes(subject: AccessUri) {
subject as UidUri
- state.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
+ newState.userStates[subject.userId]?.uidAppOpModes?.remove(subject.appId)
}
- override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.uidAppOpModes -= appId
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt b/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
new file mode 100644
index 0000000..cc51866
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/permission/ModernPermissionManagerServiceImpl.kt
@@ -0,0 +1,456 @@
+/*
+ * 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.permission.access.permission
+
+import android.content.pm.PackageManager
+import android.content.pm.PackageManagerInternal
+import android.content.pm.PermissionGroupInfo
+import android.content.pm.PermissionInfo
+import android.content.pm.permission.SplitPermissionInfoParcelable
+import android.os.Binder
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.permission.IOnPermissionsChangeListener
+import com.android.server.LocalManagerRegistry
+import com.android.server.LocalServices
+import com.android.server.pm.PackageManagerLocal
+import com.android.server.pm.permission.PermissionManagerServiceInterface
+import com.android.server.permission.access.AccessCheckingService
+import com.android.server.permission.access.PermissionUri
+import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.data.Permission
+import com.android.server.permission.access.util.hasBits
+import com.android.server.pm.permission.LegacyPermission
+import com.android.server.pm.permission.LegacyPermissionSettings
+import com.android.server.pm.permission.LegacyPermissionState
+import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.pkg.AndroidPackage
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+/**
+ * Modern implementation of [PermissionManagerServiceInterface].
+ */
+class ModernPermissionManagerServiceImpl(
+ private val service: AccessCheckingService
+) : PermissionManagerServiceInterface {
+ private val policy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as UidPermissionPolicy
+
+ private val packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal::class.java)
+
+ private val packageManagerLocal =
+ LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
+
+ override fun getAllPermissionGroups(flags: Int): List<PermissionGroupInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionGroupInfo(
+ permissionGroupName: String,
+ flags: Int
+ ): PermissionGroupInfo? {
+ val permissionGroup: PermissionGroupInfo
+ packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
+ if (snapshot.isUidInstantApp(callingUid)) {
+ return null
+ }
+
+ permissionGroup = service.getState {
+ with(policy) { getPermissionGroup(permissionGroupName) }
+ } ?: return null
+
+ val isPermissionGroupVisible =
+ snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
+ if (!isPermissionGroupVisible) {
+ return null
+ }
+ }
+
+ return permissionGroup.generatePermissionGroupInfo(flags)
+ }
+
+ /**
+ * Generate a new [PermissionGroupInfo] from [PermissionGroupInfo] and adjust it accordingly.
+ */
+ private fun PermissionGroupInfo.generatePermissionGroupInfo(flags: Int): PermissionGroupInfo =
+ @Suppress("DEPRECATION")
+ PermissionGroupInfo(this).apply {
+ if (!flags.hasBits(PackageManager.GET_META_DATA)) {
+ metaData = null
+ }
+ }
+
+ override fun getPermissionInfo(
+ permissionName: String,
+ flags: Int,
+ opPackageName: String
+ ): PermissionInfo? {
+ val permission: Permission
+ val targetSdkVersion: Int
+ packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+ val callingUid = Binder.getCallingUid()
+ if (snapshot.isUidInstantApp(callingUid)) {
+ return null
+ }
+
+ permission = service.getState {
+ with(policy) { getPermission(permissionName) }
+ } ?: return null
+
+ val isPermissionVisible =
+ snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
+ if (!isPermissionVisible) {
+ return null
+ }
+
+ val callingAppId = UserHandle.getAppId(callingUid)
+ val opPackage = snapshot.packageStates[opPackageName]?.androidPackage
+ targetSdkVersion = when {
+ // System sees all flags.
+ callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID ||
+ callingAppId == Process.SHELL_UID -> Build.VERSION_CODES.CUR_DEVELOPMENT
+ opPackage != null -> opPackage.targetSdkVersion
+ else -> Build.VERSION_CODES.CUR_DEVELOPMENT
+ }
+ }
+
+ return permission.generatePermissionInfo(flags, targetSdkVersion)
+ }
+
+ /**
+ * Generate a new [PermissionInfo] from [Permission] and adjust it accordingly.
+ */
+ private fun Permission.generatePermissionInfo(
+ flags: Int,
+ targetSdkVersion: Int
+ ): PermissionInfo =
+ @Suppress("DEPRECATION")
+ PermissionInfo(permissionInfo).apply {
+ if (!flags.hasBits(PackageManager.GET_META_DATA)) {
+ metaData = null
+ }
+ if (targetSdkVersion < Build.VERSION_CODES.O) {
+ val protection = protection
+ // Signature permission's protection flags are always reported.
+ if (protection != PermissionInfo.PROTECTION_SIGNATURE) {
+ protectionLevel = protection
+ }
+ }
+ }
+
+ override fun queryPermissionsByGroup(
+ permissionGroupName: String,
+ flags: Int
+ ): List<PermissionInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun removePermission(permissionName: String) {
+ TODO("Not yet implemented")
+ }
+
+ override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun checkUidPermission(uid: Int, permissionName: String): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun getGrantedPermissions(packageName: String, userId: Int): Set<String> {
+ TODO("Not yet implemented")
+ }
+
+ override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun revokeRuntimePermission(
+ packageName: String,
+ permissionName: String,
+ userId: Int,
+ reason: String?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun revokePostNotificationPermissionWithoutKillForTest(
+ packageName: String,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun addOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeOnPermissionsChangeListener(listener: IOnPermissionsChangeListener) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
+ TODO("Not yet implemented")
+ }
+
+ override fun isPermissionRevokedByPolicy(
+ packageName: String,
+ permissionName: String,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun isPermissionsReviewRequired(packageName: String, userId: Int): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun shouldShowRequestPermissionRationale(
+ packageName: String,
+ permissionName: String,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun updatePermissionFlags(
+ packageName: String,
+ permissionName: String,
+ flagMask: Int,
+ flagValues: Int,
+ checkAdjustPolicyFlagPermission: Boolean,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun updatePermissionFlagsForAllApps(flagMask: Int, flagValues: Int, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun addAllowlistedRestrictedPermission(
+ packageName: String,
+ permissionName: String,
+ flags: Int,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllowlistedRestrictedPermissions(
+ packageName: String,
+ flags: Int,
+ userId: Int
+ ): MutableList<String> {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeAllowlistedRestrictedPermission(
+ packageName: String,
+ permissionName: String,
+ flags: Int,
+ userId: Int
+ ): Boolean {
+ TODO("Not yet implemented")
+ }
+
+ override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun resetRuntimePermissionsForUser(userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun addOnRuntimePermissionStateChangedListener(
+ listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun removeOnRuntimePermissionStateChangedListener(
+ listener: PermissionManagerServiceInternal.OnRuntimePermissionStateChangedListener
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getSplitPermissions(): List<SplitPermissionInfoParcelable> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
+ TODO("Not yet implemented")
+ }
+
+ override fun getGidsForUid(uid: Int): IntArray {
+ TODO("Not yet implemented")
+ }
+
+ override fun backupRuntimePermissions(userId: Int): ByteArray? {
+ TODO("Not yet implemented")
+ }
+
+ override fun restoreRuntimePermissions(backup: ByteArray, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun restoreDelayedRuntimePermissions(packageName: String, userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getPermissionTEMP(
+ permissionName: String
+ ): com.android.server.pm.permission.Permission? {
+ TODO("Not yet implemented")
+ }
+
+ override fun getLegacyPermissions(): List<LegacyPermission> {
+ TODO("Not yet implemented")
+ }
+
+ override fun readLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+ TODO("Not yet implemented")
+ }
+
+ override fun writeLegacyPermissionsTEMP(legacyPermissionSettings: LegacyPermissionSettings) {
+ TODO("Not yet implemented")
+ }
+
+ override fun getLegacyPermissionState(appId: Int): LegacyPermissionState {
+ TODO("Not yet implemented")
+ }
+
+ override fun readLegacyPermissionStateTEMP() {
+ TODO("Not yet implemented")
+ }
+
+ override fun writeLegacyPermissionStateTEMP() {
+ TODO("Not yet implemented")
+ }
+
+ override fun onSystemReady() {
+ TODO("Not yet implemented")
+ }
+
+ override fun onUserCreated(userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onUserRemoved(userId: Int) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onStorageVolumeMounted(volumeUuid: String, fingerprintChanged: Boolean) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageAdded(
+ androidPackage: AndroidPackage,
+ isInstantApp: Boolean,
+ oldPackage: AndroidPackage?
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageInstalled(
+ androidPackage: AndroidPackage,
+ previousAppId: Int,
+ params: PermissionManagerServiceInternal.PackageInstalledParams,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageUninstalled(
+ packageName: String,
+ appId: Int,
+ androidPackage: AndroidPackage?,
+ sharedUserPkgs: MutableList<AndroidPackage>,
+ userId: Int
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onPackageRemoved(androidPackage: AndroidPackage) {
+ TODO("Not yet implemented")
+ }
+
+ /**
+ * Check whether a UID belongs to an instant app.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.isUidInstantApp(uid: Int): Boolean {
+ if (Process.isIsolatedUid(uid)) {
+ // Unfortunately we don't have the API for getting the owner UID of an isolated UID yet,
+ // so for now we just keep calling the old API.
+ return packageManagerInternal.getInstantAppPackageName(uid) != null
+ }
+ val appId = UserHandle.getAppId(uid)
+ // Instant apps can't have shared UIDs, so we can just take the first package.
+ val firstPackageState = packageStates.values.firstOrNull { it.appId == appId }
+ ?: return false
+ val userId = UserHandle.getUserId(uid)
+ return firstPackageState.getUserStateOrDefault(userId).isInstantApp
+ }
+
+ /**
+ * Check whether a package is visible to a UID within the same user as the UID.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
+ packageName: String,
+ uid: Int
+ ): Boolean = isPackageVisibleToUid(packageName, UserHandle.getUserId(uid), uid)
+
+ /**
+ * Check whether a package in a particular user is visible to a UID.
+ */
+ private fun PackageManagerLocal.UnfilteredSnapshot.isPackageVisibleToUid(
+ packageName: String,
+ userId: Int,
+ uid: Int
+ ): Boolean {
+ val user = UserHandle.of(userId)
+ return filtered(uid, user).use { it.getPackageState(packageName) != null }
+ }
+}
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 6479e6a..e081924 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -18,6 +18,7 @@
import android.Manifest
import android.content.pm.PackageManager
+import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
import android.os.Build
import android.os.UserHandle
@@ -26,6 +27,8 @@
import com.android.modules.utils.BinaryXmlSerializer
import com.android.server.permission.access.AccessState
import com.android.server.permission.access.AccessUri
+import com.android.server.permission.access.GetStateScope
+import com.android.server.permission.access.MutateStateScope
import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.SchemePolicy
import com.android.server.permission.access.SystemState
@@ -52,19 +55,17 @@
override val objectScheme: String
get() = PermissionUri.SCHEME
- override fun getDecision(subject: AccessUri, `object`: AccessUri, state: AccessState): Int {
+ override fun GetStateScope.getDecision(subject: AccessUri, `object`: AccessUri): Int {
subject as UidUri
`object` as PermissionUri
return state.userStates[subject.userId]?.permissionFlags?.get(subject.appId)
?.get(`object`.permissionName) ?: 0
}
- override fun setDecision(
+ override fun MutateStateScope.setDecision(
subject: AccessUri,
`object`: AccessUri,
- decision: Int,
- oldState: AccessState,
- newState: AccessState
+ decision: Int
) {
subject as UidUri
`object` as PermissionUri
@@ -73,64 +74,58 @@
uidFlags[`object`.permissionName] = decision
}
- override fun onUserAdded(userId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onUserAdded(userId: Int) {
newState.systemState.packageStates.forEachValueIndexed { _, packageState ->
- evaluateAllPermissionStatesForPackageAndUser(
- packageState, null, userId, oldState, newState
- )
- grantImplicitPermissions(packageState, userId, oldState, newState)
+ evaluateAllPermissionStatesForPackageAndUser(packageState, null, userId)
+ grantImplicitPermissions(packageState, userId)
}
}
- override fun onAppIdAdded(appId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onAppIdAdded(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState ->
userState.permissionFlags.getOrPut(appId) { IndexedMap() }
}
}
- override fun onAppIdRemoved(appId: Int, oldState: AccessState, newState: AccessState) {
+ override fun MutateStateScope.onAppIdRemoved(appId: Int) {
newState.userStates.forEachIndexed { _, _, userState -> userState.permissionFlags -= appId }
}
- override fun onPackageAdded(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {
+ override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
val changedPermissionNames = IndexedSet<String>()
- adoptPermissions(packageState, changedPermissionNames, newState)
- addPermissionGroups(packageState, newState)
- addPermissions(packageState, changedPermissionNames, newState)
+ adoptPermissions(packageState, changedPermissionNames)
+ addPermissionGroups(packageState)
+ addPermissions(packageState, changedPermissionNames)
// TODO: revokeStoragePermissionsIfScopeExpandedInternal()
- trimPermissions(packageState.packageName, newState)
- changedPermissionNames.forEachIndexed { _, it ->
- evaluatePermissionStateForAllPackages(it, packageState, oldState, newState)
+ trimPermissions(packageState.packageName)
+ changedPermissionNames.forEachIndexed { _, permissionName ->
+ evaluatePermissionStateForAllPackages(permissionName, packageState)
}
- evaluateAllPermissionStatesForPackage(packageState, packageState, oldState, newState)
- newState.systemState.userIds.forEachIndexed { _, it ->
- grantImplicitPermissions(packageState, it, oldState, newState)
+ evaluateAllPermissionStatesForPackage(packageState, packageState)
+ newState.systemState.userIds.forEachIndexed { _, userId ->
+ grantImplicitPermissions(packageState, userId)
}
// TODO: add trimPermissionStates() here for removing the permission states that are
// no longer requested. (equivalent to revokeUnusedSharedUserPermissionsLocked())
}
- private fun adoptPermissions(
+ private fun MutateStateScope.adoptPermissions(
packageState: PackageState,
- changedPermissionNames: IndexedSet<String>,
- newState: AccessState
+ changedPermissionNames: IndexedSet<String>
) {
val `package` = packageState.androidPackage!!
`package`.adoptPermissions.forEachIndexed { _, originalPackageName ->
val packageName = `package`.packageName
- if (!canAdoptPermissions(packageName, originalPackageName, newState)) {
+ if (!canAdoptPermissions(packageName, originalPackageName)) {
return@forEachIndexed
}
newState.systemState.permissions.let { permissions ->
- permissions.forEachIndexed { i, permissionName, oldPermission ->
+ permissions.forEachIndexed permissions@ {
+ permissionIndex, permissionName, oldPermission ->
if (oldPermission.packageName != originalPackageName) {
- return@forEachIndexed
+ return@permissions
}
@Suppress("DEPRECATION")
val newPermissionInfo = PermissionInfo().apply {
@@ -140,16 +135,15 @@
}
val newPermission = Permission(newPermissionInfo, false, oldPermission.type, 0)
changedPermissionNames += permissionName
- permissions.setValueAt(i, newPermission)
+ permissions.setValueAt(permissionIndex, newPermission)
}
}
}
}
- private fun canAdoptPermissions(
+ private fun MutateStateScope.canAdoptPermissions(
packageName: String,
- originalPackageName: String,
- newState: AccessState
+ originalPackageName: String
): Boolean {
val originalPackageState = newState.systemState.packageStates[originalPackageName]
?: return false
@@ -170,7 +164,7 @@
return true
}
- private fun addPermissionGroups(packageState: PackageState, newState: AccessState) {
+ private fun MutateStateScope.addPermissionGroups(packageState: PackageState) {
// Different from the old implementation, which decides whether the app is an instant app by
// the install flags, now for consistent behavior we allow adding permission groups if the
// app is non-instant in at least one user.
@@ -202,10 +196,9 @@
}
}
- private fun addPermissions(
+ private fun MutateStateScope.addPermissions(
packageState: PackageState,
- changedPermissionNames: IndexedSet<String>,
- newState: AccessState
+ changedPermissionNames: IndexedSet<String>
) {
packageState.androidPackage!!.permissions.forEachIndexed { _, parsedPermission ->
// TODO:
@@ -229,7 +222,7 @@
// Different from the old implementation, which may add an (incomplete) signature
// permission inside another package's permission tree, we now consistently ignore such
// permissions.
- val permissionTree = getPermissionTree(permissionName, newState)
+ val permissionTree = getPermissionTree(permissionName)
val newPackageName = newPermissionInfo.packageName
if (permissionTree != null && newPackageName != permissionTree.packageName) {
Log.w(
@@ -294,10 +287,7 @@
}
}
- private fun trimPermissions(
- packageName: String,
- newState: AccessState,
- ) {
+ private fun MutateStateScope.trimPermissions(packageName: String) {
val packageState = newState.systemState.packageStates[packageName]
val androidPackage = packageState?.androidPackage
if (packageState != null && androidPackage == null) {
@@ -314,20 +304,20 @@
}
newState.systemState.permissions.removeAllIndexed { i, permissionName, permission ->
- val updatedPermission = updatePermissionIfDynamic(permission, newState)
+ val updatedPermission = updatePermissionIfDynamic(permission)
newState.systemState.permissions.setValueAt(i, updatedPermission)
if (updatedPermission.packageName == packageName && (
packageState == null || androidPackage!!.permissions.noneIndexed { _, it ->
!it.isTree && it.name == permissionName
}
)) {
- if (!isPermissionDeclaredByDisabledSystemPackage(permission, newState)) {
+ if (!isPermissionDeclaredByDisabledSystemPackage(permission)) {
newState.userStates.forEachIndexed { _, userId, userState ->
userState.permissionFlags.forEachKeyIndexed { _, appId ->
setPermissionFlags(
appId, permissionName, getPermissionFlags(
- appId, permissionName, userId, newState
- ) and PermissionFlags.INSTALL_REVOKED, userId, newState
+ appId, permissionName, userId
+ ) and PermissionFlags.INSTALL_REVOKED, userId
)
}
}
@@ -339,9 +329,8 @@
}
}
- private fun isPermissionDeclaredByDisabledSystemPackage(
- permission: Permission,
- newState: AccessState
+ private fun MutateStateScope.isPermissionDeclaredByDisabledSystemPackage(
+ permission: Permission
): Boolean {
val disabledSystemPackage = newState.systemState
.disabledSystemPackageStates[permission.packageName]?.androidPackage ?: return false
@@ -350,14 +339,11 @@
}
}
- private fun updatePermissionIfDynamic(
- permission: Permission,
- newState: AccessState
- ): Permission {
+ private fun MutateStateScope.updatePermissionIfDynamic(permission: Permission): Permission {
if (!permission.isDynamic) {
return permission
}
- val permissionTree = getPermissionTree(permission.name, newState) ?: return permission
+ val permissionTree = getPermissionTree(permission.name) ?: return permission
@Suppress("DEPRECATION")
return permission.copy(
permissionInfo = PermissionInfo(permission.permissionInfo).apply {
@@ -366,7 +352,7 @@
)
}
- private fun getPermissionTree(permissionName: String, newState: AccessState): Permission? =
+ private fun MutateStateScope.getPermissionTree(permissionName: String): Permission? =
newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
_, permissionTreeName, permissionTree ->
if (permissionName.startsWith(permissionTreeName) &&
@@ -378,58 +364,48 @@
}
}
- private fun evaluatePermissionStateForAllPackages(
+ private fun MutateStateScope.evaluatePermissionStateForAllPackages(
permissionName: String,
- installedPackageState: PackageState?,
- oldState: AccessState,
- newState: AccessState
+ installedPackageState: PackageState?
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
oldState.userStates[userId]?.permissionFlags?.forEachIndexed {
_, appId, permissionFlags ->
if (permissionName in permissionFlags) {
- evaluatePermissionState(
- appId, permissionName, installedPackageState, userId, oldState, newState
- )
+ evaluatePermissionState(appId, permissionName, installedPackageState, userId)
}
}
}
}
- private fun evaluateAllPermissionStatesForPackage(
+ private fun MutateStateScope.evaluateAllPermissionStatesForPackage(
packageState: PackageState,
- installedPackageState: PackageState?,
- oldState: AccessState,
- newState: AccessState
+ installedPackageState: PackageState?
) {
newState.systemState.userIds.forEachIndexed { _, userId ->
evaluateAllPermissionStatesForPackageAndUser(
- packageState, installedPackageState, userId, oldState, newState
+ packageState, installedPackageState, userId
)
}
}
- private fun evaluateAllPermissionStatesForPackageAndUser(
+ private fun MutateStateScope.evaluateAllPermissionStatesForPackageAndUser(
packageState: PackageState,
installedPackageState: PackageState?,
- userId: Int,
- oldState: AccessState,
- newState: AccessState
+ userId: Int
) {
- packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, it ->
+ packageState.androidPackage?.requestedPermissions?.forEachIndexed { _, permissionName ->
evaluatePermissionState(
- packageState.appId, it, installedPackageState, userId, oldState, newState
+ packageState.appId, permissionName, installedPackageState, userId
)
}
}
- private fun evaluatePermissionState(
+ private fun MutateStateScope.evaluatePermissionState(
appId: Int,
permissionName: String,
installedPackageState: PackageState?,
- userId: Int,
- oldState: AccessState,
- newState: AccessState
+ userId: Int
) {
val packageNames = newState.systemState.appIds[appId]
val hasMissingPackage = packageNames.anyIndexed { _, packageName ->
@@ -440,17 +416,17 @@
return
}
val permission = newState.systemState.permissions[permissionName] ?: return
- val oldFlags = getPermissionFlags(appId, permissionName, userId, newState)
+ val oldFlags = getPermissionFlags(appId, permissionName, userId)
if (permission.isNormal) {
val wasGranted = oldFlags.hasBits(PermissionFlags.INSTALL_GRANTED)
if (!wasGranted) {
val wasRevoked = oldFlags.hasBits(PermissionFlags.INSTALL_REVOKED)
val isRequestedByInstalledPackage = installedPackageState != null &&
permissionName in installedPackageState.androidPackage!!.requestedPermissions
- val isRequestedBySystemPackage = anyPackageInAppId(appId, newState) {
+ val isRequestedBySystemPackage = anyPackageInAppId(appId) {
it.isSystem && permissionName in it.androidPackage!!.requestedPermissions
}
- val isCompatibilityPermission = anyPackageInAppId(appId, newState) {
+ val isCompatibilityPermission = anyPackageInAppId(appId) {
isCompatibilityPermissionForPackage(it.androidPackage!!, permissionName)
}
// If this is an existing, non-system package,
@@ -462,7 +438,7 @@
} else {
PermissionFlags.INSTALL_REVOKED
}
- setPermissionFlags(appId, permissionName, newFlags, userId, newState)
+ setPermissionFlags(appId, permissionName, newFlags, userId)
}
} else if (permission.isSignature || permission.isInternal) {
val wasProtectionGranted = oldFlags.hasBits(PermissionFlags.PROTECTION_GRANTED)
@@ -471,17 +447,17 @@
PermissionFlags.PROTECTION_GRANTED
} else {
val mayGrantByPrivileged = !permission.isPrivileged || (
- anyPackageInAppId(appId, newState) {
- checkPrivilegedPermissionAllowlist(it, permission, newState)
+ anyPackageInAppId(appId) {
+ checkPrivilegedPermissionAllowlist(it, permission)
}
)
val shouldGrantBySignature = permission.isSignature && (
- anyPackageInAppId(appId, newState) {
- shouldGrantPermissionBySignature(it, permission, newState)
+ anyPackageInAppId(appId) {
+ shouldGrantPermissionBySignature(it, permission)
}
)
- val shouldGrantByProtectionFlags = anyPackageInAppId(appId, newState) {
- shouldGrantPermissionByProtectionFlags(it, permission, newState)
+ val shouldGrantByProtectionFlags = anyPackageInAppId(appId) {
+ shouldGrantPermissionByProtectionFlags(it, permission)
}
if (mayGrantByPrivileged &&
(shouldGrantBySignature || shouldGrantByProtectionFlags)) {
@@ -501,7 +477,7 @@
if (permission.isRole) {
newFlags = newFlags or (oldFlags and PermissionFlags.ROLE_GRANTED)
}
- setPermissionFlags(appId, permissionName, newFlags, userId, newState)
+ setPermissionFlags(appId, permissionName, newFlags, userId)
} else if (permission.isRuntime) {
// TODO: add runtime permissions
} else {
@@ -513,12 +489,7 @@
// TODO: revokePermissionsNoLongerImplicitLocked() for runtime permissions
}
- private fun grantImplicitPermissions(
- packageState: PackageState,
- userId: Int,
- oldState: AccessState,
- newState: AccessState
- ) {
+ private fun MutateStateScope.grantImplicitPermissions(packageState: PackageState, userId: Int) {
val appId = packageState.appId
val androidPackage = packageState.androidPackage ?: return
androidPackage.implicitPermissions.forEachIndexed implicitPermissions@ {
@@ -530,6 +501,7 @@
if (!implicitPermission.isRuntime) {
return@implicitPermissions
}
+ // Explicitly check against the old state to determine if this permission is new.
val isNewPermission = getPermissionFlags(
appId, implicitPermissionName, userId, oldState
) == 0
@@ -544,7 +516,7 @@
checkNotNull(sourcePermission) {
"Unknown source permission $sourcePermissionName in split permissions"
}
- val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId, newState)
+ val sourceFlags = getPermissionFlags(appId, sourcePermissionName, userId)
val isSourceGranted = sourceFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
val isNewGranted = newFlags.hasAnyBit(PermissionFlags.MASK_GRANTED)
val isGrantingNewFromRevoke = isSourceGranted && !isNewGranted
@@ -559,23 +531,22 @@
}
}
newFlags = newFlags or PermissionFlags.IMPLICIT
- setPermissionFlags(appId, implicitPermissionName, newFlags, userId, newState)
+ setPermissionFlags(appId, implicitPermissionName, newFlags, userId)
}
}
- private fun getPermissionFlags(
+ private fun MutateStateScope.getPermissionFlags(
appId: Int,
permissionName: String,
userId: Int,
- state: AccessState
+ state: AccessState = newState
): Int = state.userStates[userId].permissionFlags[appId].getWithDefault(permissionName, 0)
- private fun setPermissionFlags(
+ private fun MutateStateScope.setPermissionFlags(
appId: Int,
permissionName: String,
flags: Int,
- userId: Int,
- newState: AccessState
+ userId: Int
) {
newState.userStates[userId].permissionFlags[appId]!!
.putWithDefault(permissionName, flags, 0)
@@ -585,8 +556,9 @@
androidPackage: AndroidPackage,
permissionName: String
): Boolean {
- for (info: CompatibilityPermissionInfo in CompatibilityPermissionInfo.COMPAT_PERMS) {
- if (info.name == permissionName && androidPackage.targetSdkVersion < info.sdkVersion) {
+ for (compatibilityPermission in CompatibilityPermissionInfo.COMPAT_PERMS) {
+ if (compatibilityPermission.name == permissionName &&
+ androidPackage.targetSdkVersion < compatibilityPermission.sdkVersion) {
Log.i(
LOG_TAG, "Auto-granting $permissionName to old package" +
" ${androidPackage.packageName}"
@@ -597,10 +569,9 @@
return false
}
- private fun shouldGrantPermissionBySignature(
+ private fun MutateStateScope.shouldGrantPermissionBySignature(
packageState: PackageState,
- permission: Permission,
- newState: AccessState
+ permission: Permission
): Boolean {
// check if the package is allow to use this signature permission. A package is allowed to
// use a signature permission if:
@@ -622,10 +593,9 @@
SigningDetails.CertCapabilities.PERMISSION)
}
- private fun checkPrivilegedPermissionAllowlist(
+ private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
packageState: PackageState,
- permission: Permission,
- newState: AccessState
+ permission: Permission
): Boolean {
if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
return true
@@ -641,10 +611,10 @@
newState.systemState.privilegedPermissionAllowlistSourcePackageNames) {
return true
}
- if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name, newState)) {
+ if (isInSystemConfigPrivAppPermissions(androidPackage, permission.name)) {
return true
}
- if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name, newState)) {
+ if (isInSystemConfigPrivAppDenyPermissions(androidPackage, permission.name)) {
return false
}
// Updated system apps do not need to be allowlisted
@@ -655,10 +625,9 @@
return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
}
- private fun isInSystemConfigPrivAppPermissions(
+ private fun MutateStateScope.isInSystemConfigPrivAppPermissions(
androidPackage: AndroidPackage,
- permissionName: String,
- newState: AccessState
+ permissionName: String
): Boolean {
val apexModuleName = androidPackage.apexModuleName
val systemState = newState.systemState
@@ -682,10 +651,9 @@
return permissionNames?.contains(permissionName) == true
}
- private fun isInSystemConfigPrivAppDenyPermissions(
+ private fun MutateStateScope.isInSystemConfigPrivAppDenyPermissions(
androidPackage: AndroidPackage,
- permissionName: String,
- newState: AccessState
+ permissionName: String
): Boolean {
// Different from the previous implementation, which may incorrectly use the APEX package
// name, we now use the APEX module name to be consistent with the allowlist.
@@ -713,22 +681,21 @@
return permissionNames?.contains(permissionName) == true
}
- private fun anyPackageInAppId(
+ private fun MutateStateScope.anyPackageInAppId(
appId: Int,
- newState: AccessState,
+ state: AccessState = newState,
predicate: (PackageState) -> Boolean
): Boolean {
- val packageNames = newState.systemState.appIds[appId]
+ val packageNames = state.systemState.appIds[appId]
return packageNames.anyIndexed { _, packageName ->
- val packageState = newState.systemState.packageStates[packageName]!!
+ val packageState = state.systemState.packageStates[packageName]!!
packageState.androidPackage != null && predicate(packageState)
}
}
- private fun shouldGrantPermissionByProtectionFlags(
+ private fun MutateStateScope.shouldGrantPermissionByProtectionFlags(
packageState: PackageState,
- permission: Permission,
- newState: AccessState
+ permission: Permission
): Boolean {
val androidPackage = packageState.androidPackage!!
val knownPackages = newState.systemState.knownPackages
@@ -741,11 +708,9 @@
.disabledSystemPackageStates[packageState.packageName]?.androidPackage
disabledSystemPackage != null &&
permission.name in disabledSystemPackage.requestedPermissions &&
- shouldGrantPrivilegedOrOemPermission(
- disabledSystemPackage, permission, newState
- )
+ shouldGrantPrivilegedOrOemPermission(disabledSystemPackage, permission)
} else {
- shouldGrantPrivilegedOrOemPermission(androidPackage, permission, newState)
+ shouldGrantPrivilegedOrOemPermission(androidPackage, permission)
}
if (shouldGrant) {
return true
@@ -815,7 +780,7 @@
}
if (permission.isRetailDemo &&
packageName in knownPackages[KnownPackages.PACKAGE_RETAIL_DEMO] &&
- isDeviceOrProfileOwnerUid(packageState.appId, newState)) {
+ isDeviceOrProfileOwnerUid(packageState.appId)) {
// Special permission granted only to the OEM specified retail demo app.
// Note that the original code was passing app ID as UID, so this behavior is kept
// unchanged.
@@ -829,10 +794,9 @@
return false
}
- private fun shouldGrantPrivilegedOrOemPermission(
+ private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
androidPackage: AndroidPackage,
- permission: Permission,
- state: AccessState
+ permission: Permission
): Boolean {
val permissionName = permission.name
val packageName = androidPackage.packageName
@@ -855,7 +819,7 @@
}
permission.isOem -> {
if (androidPackage.isOem) {
- val isOemAllowlisted = state.systemState
+ val isOemAllowlisted = newState.systemState
.oemPermissions[packageName]?.get(permissionName)
checkNotNull(isOemAllowlisted) {
"OEM permission $permissionName requested by package" +
@@ -868,19 +832,15 @@
return false
}
- private fun isDeviceOrProfileOwnerUid(uid: Int, state: AccessState): Boolean {
+ private fun MutateStateScope.isDeviceOrProfileOwnerUid(uid: Int): Boolean {
val userId = UserHandle.getUserId(uid)
- val ownerPackageName = state.systemState.deviceAndProfileOwners[userId] ?: return false
- val ownerPackageState = state.systemState.packageStates[ownerPackageName] ?: return false
+ val ownerPackageName = newState.systemState.deviceAndProfileOwners[userId] ?: return false
+ val ownerPackageState = newState.systemState.packageStates[ownerPackageName] ?: return false
val ownerUid = UserHandle.getUid(userId, ownerPackageState.appId)
return uid == ownerUid
}
- override fun onPackageRemoved(
- packageState: PackageState,
- oldState: AccessState,
- newState: AccessState
- ) {
+ override fun MutateStateScope.onPackageRemoved(packageState: PackageState) {
// TODO
}
@@ -892,6 +852,12 @@
with(persistence) { this@serializeSystemState.serializeSystemState(systemState) }
}
+ fun GetStateScope.getPermissionGroup(permissionGroupName: String): PermissionGroupInfo? =
+ state.systemState.permissionGroups[permissionGroupName]
+
+ fun GetStateScope.getPermission(permissionName: String): Permission? =
+ state.systemState.permissions[permissionName]
+
companion object {
private val LOG_TAG = UidPermissionPolicy::class.java.simpleName
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt
new file mode 100644
index 0000000..4044780
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayPathsUninstallSystemUpdatesTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.pm.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OverlayPathsUninstallSystemUpdatesTest : BaseHostJUnit4Test() {
+
+ companion object {
+ private const val TEST_PKG_NAME = "com.android.server.pm.test.test_app"
+ private const val VERSION_ONE = "PackageManagerTestAppVersion1.apk"
+ private const val VERSION_TWO = "PackageManagerTestAppVersion2.apk"
+
+ @get:ClassRule
+ val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+ }
+
+ private val tempFolder = TemporaryFolder()
+ private val preparer: SystemPreparer = SystemPreparer(tempFolder,
+ SystemPreparer.RebootStrategy.FULL, deviceRebootRule, true) { this.device }
+
+ @Rule
+ @JvmField
+ val rules = RuleChain.outerRule(tempFolder).around(preparer)!!
+ private val filePath = HostUtils.makePathForApk("PackageManagerTestApp.apk", Partition.PRODUCT)
+
+ @Before
+ @After
+ fun removeApk() {
+ device.uninstallPackage(TEST_PKG_NAME)
+ }
+
+ @Test
+ fun verify() {
+ // First, push a system app to the device and then update it so there's a data variant
+ preparer.pushResourceFile(VERSION_ONE, filePath.toString())
+ .reboot()
+
+ val versionTwoFile = HostUtils.copyResourceToHostFile(VERSION_TWO, tempFolder.newFile())
+
+ assertThat(device.installPackage(versionTwoFile, true)).isNull()
+
+ device.executeShellCommand(
+ "cmd overlay fabricate --target-name TestResources" +
+ " --target $TEST_PKG_NAME" +
+ " --name UninstallSystemUpdatesTest" +
+ " $TEST_PKG_NAME:color/overlay_test 0x1C 0xFFFFFFFF"
+ )
+
+ device.executeShellCommand(
+ "cmd overlay enable --user 0 com.android.shell:UninstallSystemUpdatesTest"
+ )
+
+ fun verifyValueOverlaid() {
+ assertThat(device.executeShellCommand(
+ "cmd overlay lookup --user 0 $TEST_PKG_NAME $TEST_PKG_NAME:color/overlay_test"
+ ).trim()).isEqualTo("#ffffffff")
+ }
+
+ verifyValueOverlaid()
+
+ assertThat(
+ device.executeShellCommand("pm uninstall-system-updates $TEST_PKG_NAME"
+ ).trim()).endsWith("Success")
+
+ // Wait for paths to re-propagate. This doesn't do a retry loop in case the path clear also
+ // has some latency. There must be some minimum wait time for the paths to settle, and then
+ // a wait time for the paths to re-propagate. Rather than complicate the logic, just wait
+ // a long enough time for both events to occur.
+ Thread.sleep(5000)
+
+ verifyValueOverlaid()
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml
new file mode 100644
index 0000000..5a41d88
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/colors.xml
@@ -0,0 +1,19 @@
+<?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>
+ <color name="overlay_test">#FF000000</color>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml
new file mode 100644
index 0000000..c5ba450
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/res/values/overlayable.xml
@@ -0,0 +1,23 @@
+<?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>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="color" name="overlay_test" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
index faa2352..5f26d6f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -53,6 +53,11 @@
snapshot.use {
val packageStates = it.packageStates
+ // Check for unmodifiable
+ assertFailsWith(UnsupportedOperationException::class) {
+ it.packageStates.clear()
+ }
+
// Check contents
assertThat(packageStates).containsExactly(
packageStateAll.packageName, packageStateAll,
@@ -78,9 +83,14 @@
assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
filteredThree.use {
- val statesList = mutableListOf<PackageState>()
- assertThat(it.forAllPackageStates { statesList += it })
- assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+ // Check for unmodifiable
+ assertFailsWith(UnsupportedOperationException::class) {
+ it.packageStates.clear()
+ }
+ assertThat(it.packageStates).containsExactly(
+ packageStateAll.packageName, packageStateAll,
+ packageStateUser10.packageName, packageStateUser10,
+ )
}
// Call after child close, parent open fails
@@ -96,7 +106,7 @@
// Call after close fails
assertClosedFailure { snapshot.packageStates }
- assertClosedFailure { filteredOne.forAllPackageStates {} }
+ assertClosedFailure { filteredOne.packageStates }
assertClosedFailure {
filteredTwo.getPackageState(packageStateAll.packageName)
}
@@ -116,9 +126,15 @@
.isEqualTo(packageStateUser0)
assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
- val statesList = mutableListOf<PackageState>()
- assertThat(it.forAllPackageStates { statesList += it })
- assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+ // Check for unmodifiable
+ assertFailsWith(UnsupportedOperationException::class) {
+ it.packageStates.clear()
+ }
+
+ assertThat(it.packageStates).containsExactly(
+ packageStateAll.packageName, packageStateAll,
+ packageStateUser0.packageName, packageStateUser0,
+ )
}
// Call after close fails
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 c87fd26..de09b19 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -592,6 +592,73 @@
}
/**
+ * Verify that offload broadcasts are not starved because of broadcasts in higher priority
+ * queues.
+ */
+ @Test
+ public void testOffloadStarvation() {
+ final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+ optInteractive.setInteractive(true);
+
+ mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1;
+ mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2;
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ // mix of broadcasts, with more than 2 normal
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+ optInteractive), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+ optInteractive), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+ optInteractive), 0);
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+ queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+ // expect an offload one
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 395e6ac..f105971 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -23,6 +23,7 @@
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.assertTrue;
@@ -58,6 +59,7 @@
import com.google.common.truth.Truth;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -84,6 +86,8 @@
private static final long HANDLER_WAIT_MS = 100;
+ private static final int[] HDR_TYPES = new int[]{1, 2};
+
private StaticMockitoSession mMockitoSession;
private LocalDisplayAdapter mAdapter;
@@ -202,6 +206,38 @@
PORT_C, false);
}
+ @Test
+ public void testSupportedDisplayModesGetOverriddenWhenDisplayIsUpdated()
+ throws InterruptedException {
+ SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
+ displayMode.supportedHdrTypes = new int[0];
+ FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
+ 0, 0);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ Display.Mode[] supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+ Assert.assertEquals(1, supportedModes.length);
+ Assert.assertEquals(0, supportedModes[0].getSupportedHdrTypes().length);
+
+ displayMode.supportedHdrTypes = new int[]{3, 2};
+ display.dynamicInfo.supportedDisplayModes = new SurfaceControl.DisplayMode[]{displayMode};
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ supportedModes = displayDevice.getDisplayDeviceInfoLocked().supportedModes;
+
+ Assert.assertEquals(1, supportedModes.length);
+ assertArrayEquals(new int[]{2, 3}, supportedModes[0].getSupportedHdrTypes());
+ }
+
/**
* Confirm that all local displays are public when config_localPrivateDisplayPorts is empty.
*/
@@ -357,7 +393,7 @@
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -413,7 +449,7 @@
SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
SurfaceControl.DisplayMode[] modes =
new SurfaceControl.DisplayMode[]{displayMode};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -468,7 +504,7 @@
createFakeDisplayMode(0, 1920, 1080, 60f),
createFakeDisplayMode(1, 1920, 1080, 50f)
};
- FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
setUpDisplay(display);
updateAvailableDisplays();
mAdapter.registerLocked();
@@ -502,6 +538,49 @@
}
@Test
+ public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+ SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+ createFakeDisplayMode(0, 1920, 1080, 60f),
+ };
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+ /* renderFrameRate */30f);
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays).isEmpty();
+
+ DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+ .getDisplayDeviceInfoLocked();
+
+ Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+ assertEquals(Float.floatToIntBits(30f),
+ Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+ // Change the render frame rate
+ display.dynamicInfo.renderFrameRate = 60f;
+ setUpDisplay(display);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+ assertTrue(mListener.traversalRequested);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+ DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+ displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+ displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+ activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+ assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+ assertEquals(Float.floatToIntBits(60f),
+ Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+ }
+
+ @Test
public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
FakeDisplay display = new FakeDisplay(PORT_A);
Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
@@ -686,7 +765,7 @@
createFakeDisplayMode(1, 1920, 1080, 50f)
};
final int activeMode = 0;
- FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode);
+ FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
display.desiredDisplayModeSpecs.defaultMode = 1;
setUpDisplay(display);
@@ -943,11 +1022,13 @@
dynamicInfo.activeDisplayModeId = 0;
}
- private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode) {
+ private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+ float renderFrameRate) {
address = createDisplayAddress(port);
info = createFakeDisplayInfo();
dynamicInfo.supportedDisplayModes = modes;
dynamicInfo.activeDisplayModeId = activeMode;
+ dynamicInfo.renderFrameRate = renderFrameRate;
}
private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
@@ -1008,6 +1089,7 @@
mode.xDpi = 100;
mode.yDpi = 100;
mode.group = group;
+ mode.supportedHdrTypes = HDR_TYPES;
return mode;
}
diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
new file mode 100644
index 0000000..5335f96
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.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.
+ -->
+
+<device name="Android">
+ <!-- Cellular modem related values. These constants are deprecated, but still supported and
+ need to be tested -->
+ <item name="radio.scanning">720</item>
+ <item name="modem.controller.sleep">70</item>
+ <item name="modem.controller.idle">360</item>
+ <item name="modem.controller.rx">1440</item>
+ <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+ <value>720</value>
+ <value>1080</value>
+ <value>1440</value>
+ <value>1800</value>
+ <value>2160</value>
+ </array>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
new file mode 100644
index 0000000..f57bc0f
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.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.
+ -->
+
+<device name="Android">
+ <modem>
+ <sleep>70</sleep>
+ <idle>360</idle>
+ <active rat="DEFAULT">
+ <receive>1440</receive>
+ <transmit level="0">720</transmit>
+ <transmit level="1">1080</transmit>
+ <transmit level="2">1440</transmit>
+ <transmit level="3">1800</transmit>
+ <transmit level="4">2160</transmit>
+ </active>
+ </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
new file mode 100644
index 0000000..4f5e674
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
@@ -0,0 +1,63 @@
+<?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.
+ -->
+
+<device name="Android">
+ <modem>
+ <sleep>70</sleep>
+ <idle>360</idle>
+ <active rat="DEFAULT">
+ <receive>1440</receive>
+ <transmit level="0">720</transmit>
+ <transmit level="1">1080</transmit>
+ <transmit level="2">1440</transmit>
+ <transmit level="3">1800</transmit>
+ <transmit level="4">2160</transmit>
+ </active>
+ <active rat="LTE">
+ <receive>2000</receive>
+ <transmit level="0">800</transmit>
+ <transmit level="1">1200</transmit>
+ <transmit level="2">1600</transmit>
+ <transmit level="3">2000</transmit>
+ <transmit level="4">2400</transmit>
+ </active>
+ <active rat="NR" nrFrequency="DEFAULT">
+ <receive>2222</receive>
+ <transmit level="0">999</transmit>
+ <transmit level="1">1333</transmit>
+ <transmit level="2">1888</transmit>
+ <transmit level="3">2222</transmit>
+ <transmit level="4">2666</transmit>
+ </active>
+ <active rat="NR" nrFrequency="HIGH">
+ <receive>2727</receive>
+ <transmit level="0">1818</transmit>
+ <transmit level="1">2727</transmit>
+ <transmit level="2">3636</transmit>
+ <transmit level="3">4545</transmit>
+ <transmit level="4">5454</transmit>
+ </active>
+ <active rat="NR" nrFrequency="MMWAVE">
+ <receive>3456</receive>
+ <transmit level="0">2345</transmit>
+ <transmit level="1">3456</transmit>
+ <transmit level="2">4567</transmit>
+ <transmit level="3">5678</transmit>
+ <transmit level="4">6789</transmit>
+ </active>
+ </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
new file mode 100644
index 0000000..ab016fb
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
@@ -0,0 +1,35 @@
+<?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.
+ -->
+
+<device name="Android">
+ <modem>
+ <!-- Modem sleep drain current value in mA. -->
+ <sleep>10</sleep>
+ <!-- Modem idle drain current value in mA. -->
+ <idle>20</idle>
+ <active rat="DEFAULT">
+ <!-- Transmit current drain in mA. -->
+ <receive>30</receive>
+ <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+ <transmit level="0">40</transmit>
+ <transmit level="1">50</transmit>
+ <transmit level="2">60</transmit>
+ <transmit level="3">70</transmit>
+ <transmit level="4">80</transmit>
+ </active>
+ </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..cbe6d26
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.server;
+
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.net.NetworkPolicyManager;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.NetworkManagementService.Dependencies;
+import com.android.server.net.BaseNetworkObserver;
+
+import org.junit.After;
+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.function.BiFunction;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkManagementServiceTest {
+ private NetworkManagementService mNMService;
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
+ @Mock private IBatteryStats.Stub mBatteryStatsService;
+ @Mock private INetd.Stub mNetdService;
+
+ private static final int TEST_UID = 111;
+
+ @NonNull
+ @Captor
+ private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
+
+ private final MockDependencies mDeps = new MockDependencies();
+
+ private final class MockDependencies extends Dependencies {
+ @Override
+ public IBinder getService(String name) {
+ switch (name) {
+ case BatteryStats.SERVICE_NAME:
+ return mBatteryStatsService;
+ default:
+ throw new UnsupportedOperationException("Unknown service " + name);
+ }
+ }
+
+ @Override
+ public void registerLocalService(NetworkManagementInternal nmi) {
+ }
+
+ @Override
+ public INetd getNetd() {
+ return mNetdService;
+ }
+
+ @Override
+ public int getCallingUid() {
+ return Process.SYSTEM_UID;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doNothing().when(mNetdService)
+ .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+ doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+ eq(ConnectivityManager.class));
+ doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+ // Start the service and wait until it connects to our socket.
+ mNMService = NetworkManagementService.create(mContext, mDeps);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mNMService.shutdown();
+ }
+
+ private static <T> T expectSoon(T mock) {
+ return verify(mock, timeout(200));
+ }
+
+ /**
+ * Tests that network observers work properly.
+ */
+ @Test
+ public void testNetworkObservers() throws Exception {
+ BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
+ doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver.
+ mNMService.registerObserver(observer);
+
+ // Forget everything that happened to the mock so far, so we can explicitly verify
+ // everything that happens and does not happen to it from now on.
+
+ INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
+ reset(observer);
+ // Now call unsolListener methods and ensure that the observer methods are
+ // called. After every method we expect a callback soon after; to ensure that
+ // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
+
+ /**
+ * Interface changes.
+ */
+ unsolListener.onInterfaceAdded("rmnet12");
+ expectSoon(observer).interfaceAdded("rmnet12");
+
+ unsolListener.onInterfaceRemoved("eth1");
+ expectSoon(observer).interfaceRemoved("eth1");
+
+ unsolListener.onInterfaceChanged("clat4", true);
+ expectSoon(observer).interfaceStatusChanged("clat4", true);
+
+ unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
+ expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
+
+ /**
+ * Bandwidth control events.
+ */
+ unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
+ expectSoon(observer).limitReached("data", "rmnet_usb0");
+
+ /**
+ * Interface class activity.
+ */
+ unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
+ expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
+
+ unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
+ expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
+
+ unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
+ expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
+
+ /**
+ * IP address changes.
+ */
+ unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
+ expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+ unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
+ expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+ unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
+ expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
+
+ /**
+ * DNS information broadcasts.
+ */
+ unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
+ expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
+ new String[]{"2001:db8::1"});
+
+ unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
+ new String[]{"2001:db8::1", "2001:db8::2"});
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
+ new String[]{"2001:db8::1", "2001:db8::2"});
+
+ // We don't check for negative lifetimes, only for parse errors.
+ unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
+ new String[]{"::1"});
+
+ // No syntax checking on the addresses.
+ unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
+ new String[]{"", "::", "", "foo", "::1"});
+ expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
+ new String[]{"", "::", "", "foo", "::1"});
+
+ // Make sure nothing else was called.
+ verifyNoMoreInteractions(observer);
+ }
+
+ @Test
+ public void testFirewallEnabled() {
+ mNMService.setFirewallEnabled(true);
+ assertTrue(mNMService.isFirewallEnabled());
+
+ mNMService.setFirewallEnabled(false);
+ assertFalse(mNMService.isFirewallEnabled());
+ }
+
+ @Test
+ public void testNetworkRestrictedDefault() {
+ assertFalse(mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
+ public void testMeteredNetworkRestrictions() throws RemoteException {
+ // Make sure the mocked netd method returns true.
+ doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
+
+ // Restrict usage of mobile data in background
+ mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
+ assertTrue("Should be true since mobile data usage is restricted",
+ mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
+
+ mNMService.setDataSaverModeEnabled(true);
+ verify(mNetdService).bandwidthEnableDataSaver(true);
+
+ mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
+ assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+ mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
+
+ mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
+ assertFalse("Should be false since data saver is on and the uid is allowlisted",
+ mNMService.isNetworkRestricted(TEST_UID));
+ verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
+
+ // remove uid from allowlist and turn datasaver off again
+ mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+ verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
+ mNMService.setDataSaverModeEnabled(false);
+ verify(mNetdService).bandwidthEnableDataSaver(false);
+ assertFalse("Network should not be restricted when data saver is off",
+ mNMService.isNetworkRestricted(TEST_UID));
+ }
+
+ @Test
+ public void testFirewallChains() {
+ final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+ // Dozable chain
+ final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+ isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+ // Powersaver chain
+ final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+ isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+ // Standby chain
+ final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+ isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
+ isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+ // Restricted mode chain
+ final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
+ isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+ // Low Power Standby chain
+ final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
+ isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+ isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+ isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
+ expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+
+ final int[] chains = {
+ FIREWALL_CHAIN_STANDBY,
+ FIREWALL_CHAIN_POWERSAVE,
+ FIREWALL_CHAIN_DOZABLE,
+ FIREWALL_CHAIN_RESTRICTED,
+ FIREWALL_CHAIN_LOW_POWER_STANDBY
+ };
+ final int[] states = {
+ INetd.FIREWALL_RULE_ALLOW,
+ INetd.FIREWALL_RULE_DENY,
+ NetworkPolicyManager.FIREWALL_RULE_DEFAULT
+ };
+ BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+ return String.format("Unexpected value for chain: %s and state: %s",
+ valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
+ valueToString(INetd.class, "FIREWALL_RULE_", state));
+ };
+ for (int chain : chains) {
+ final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+ mNMService.setFirewallChainEnabled(chain, true);
+ verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
+ for (int state : states) {
+ mNMService.setFirewallUidRule(chain, TEST_UID, state);
+ assertEquals(errorMsg.apply(chain, state),
+ expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
+ }
+ mNMService.setFirewallChainEnabled(chain, false);
+ verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
+ }
+ }
+}
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 6e446f0..d47f063 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -638,6 +638,39 @@
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
}
+ @Test
+ public void testStopUser_invalidUser() {
+ int userId = -1;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mUserController.stopUser(userId, /* force= */ true,
+ /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null));
+ }
+
+ @Test
+ public void testStopUser_systemUser() {
+ int userId = UserHandle.USER_SYSTEM;
+
+ int r = mUserController.stopUser(userId, /* force= */ true,
+ /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null);
+
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+ }
+
+ @Test
+ public void testStopUser_currentUser() {
+ setUpUser(TEST_USER_ID1, /* flags= */ 0);
+ mUserController.startUser(TEST_USER_ID1, /* foreground= */ true);
+
+ int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+ /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+ /* keyEvictedCallback= */ null);
+
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_IS_CURRENT);
+ }
+
/**
* Test conditional delayed locking with mDelayUserDataLocking true.
*/
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 3daf0f8..9de6190 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
@@ -58,15 +58,18 @@
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.graphics.Point;
import android.hardware.Sensor;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
+import android.hardware.input.VirtualDpadConfig;
import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboardConfig;
import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreenConfig;
import android.net.MacAddress;
import android.os.Binder;
import android.os.Handler;
@@ -135,7 +138,37 @@
private static final int SENSOR_HANDLE = 64;
private static final Binder BINDER = new Binder("binder");
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
- private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final VirtualDpadConfig DPAD_CONFIG =
+ new VirtualDpadConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ private static final VirtualKeyboardConfig KEYBOARD_CONFIG =
+ new VirtualKeyboardConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ private static final VirtualMouseConfig MOUSE_CONFIG =
+ new VirtualMouseConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .build();
+ private static final VirtualTouchscreenConfig TOUCHSCREEN_CONFIG =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(WIDTH)
+ .setHeightInPixels(HEIGHT)
+ .build();
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
@@ -477,63 +510,77 @@
@Test
public void createVirtualDpad_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
}
@Test
public void createVirtualKeyboard_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
}
@Test
public void createVirtualMouse_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
}
@Test
public void createVirtualTouchscreen_noDisplay_failsSecurityException() {
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
- VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
}
@Test
public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- Point size = new Point(0, 0);
+ final VirtualTouchscreenConfig zeroConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(0)
+ .setHeightInPixels(0)
+ .build();
assertThrows(IllegalArgumentException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER, size));
+ () -> mDeviceImpl.createVirtualTouchscreen(zeroConfig, BINDER));
}
@Test
public void createVirtualTouchscreen_negativeDisplayDimension_failsIllegalArgumentException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- Point size = new Point(-100, -100);
+ final VirtualTouchscreenConfig negativeConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(-100)
+ .setHeightInPixels(-100)
+ .build();
assertThrows(IllegalArgumentException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER, size));
+ () -> mDeviceImpl.createVirtualTouchscreen(negativeConfig, BINDER));
+
}
@Test
public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- Point size = new Point(600, 800);
- mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER,
- size);
+ VirtualTouchscreenConfig positiveConfig =
+ new VirtualTouchscreenConfig.Builder()
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .setInputDeviceName(DEVICE_NAME)
+ .setAssociatedDisplayId(DISPLAY_ID)
+ .setWidthInPixels(600)
+ .setHeightInPixels(800)
+ .build();
+ mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
assertWithMessage(
- "Virtual touchscreen should create input device descriptor on successful creation.")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ "Virtual touchscreen should create input device descriptor on successful creation"
+ + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
}
@Test
@@ -548,10 +595,8 @@
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
}
@Test
@@ -559,10 +604,8 @@
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
}
@Test
@@ -570,10 +613,8 @@
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID,
- PRODUCT_ID, BINDER));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
}
@Test
@@ -581,10 +622,8 @@
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
- assertThrows(
- SecurityException.class,
- () -> mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME,
- VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
+ assertThrows(SecurityException.class,
+ () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
}
@Test
@@ -619,21 +658,19 @@
@Test
public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualDpad(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER);
- assertWithMessage("Virtual dpad should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
- verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID),
- eq(PRODUCT_ID), anyString());
+ mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
+ assertWithMessage("Virtual dpad should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
+ verify(mNativeWrapperMock).openUinputDpad(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
+ anyString());
}
@Test
public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER);
- assertWithMessage("Virtual keyboard should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
+ assertWithMessage("Virtual keyboard should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString());
}
@@ -641,10 +678,9 @@
@Test
public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER);
- assertWithMessage("Virtual mouse should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
+ assertWithMessage("Virtual mouse should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputMouse(eq(DEVICE_NAME), eq(VENDOR_ID), eq(PRODUCT_ID),
anyString());
}
@@ -652,10 +688,9 @@
@Test
public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
- mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID,
- BINDER, new Point(WIDTH, HEIGHT));
- assertWithMessage("Virtual touchscreen should register fd when the display matches")
- .that(mInputController.mInputDeviceDescriptors).isNotEmpty();
+ mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
+ assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
+ mInputController.getInputDeviceDescriptors()).isNotEmpty();
verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
}
@@ -909,9 +944,28 @@
mDeviceImpl.mVirtualDisplayIds.add(1);
mDeviceImpl.mVirtualDisplayIds.add(2);
mDeviceImpl.mVirtualDisplayIds.add(3);
- mDeviceImpl.createVirtualMouse(1, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
- mDeviceImpl.createVirtualMouse(2, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
- mDeviceImpl.createVirtualMouse(3, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER);
+ VirtualMouseConfig config1 = new VirtualMouseConfig.Builder()
+ .setAssociatedDisplayId(1)
+ .setInputDeviceName(DEVICE_NAME)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .build();
+ VirtualMouseConfig config2 = new VirtualMouseConfig.Builder()
+ .setAssociatedDisplayId(2)
+ .setInputDeviceName(DEVICE_NAME)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .build();
+ VirtualMouseConfig config3 = new VirtualMouseConfig.Builder()
+ .setAssociatedDisplayId(3)
+ .setInputDeviceName(DEVICE_NAME)
+ .setVendorId(VENDOR_ID)
+ .setProductId(PRODUCT_ID)
+ .build();
+
+ mDeviceImpl.createVirtualMouse(config1, BINDER);
+ mDeviceImpl.createVirtualMouse(config2, BINDER);
+ mDeviceImpl.createVirtualMouse(config3, BINDER);
mDeviceImpl.setShowPointerIcon(false);
verify(mInputManagerInternalMock, times(3)).setPointerIconVisible(eq(false), anyInt());
verify(mInputManagerInternalMock, never()).setPointerIconVisible(eq(true), anyInt());
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 3ca648c..aaa1351 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
@@ -16,7 +16,6 @@
package com.android.server.companion.virtual.audio;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.media.AudioAttributes.FLAG_SECURE;
import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -86,8 +85,9 @@
/* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
- /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING,
- /* displayCategories= */ new ArrayList<>());
+ /* displayCategories= */ new ArrayList<>(),
+ /* recentsPolicy= */
+ VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS);
}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
index c2a81d9..d4e3d44 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncManagerTest.java
@@ -16,12 +16,40 @@
package com.android.server.content;
+import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT;
+import static android.content.pm.UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT;
+import static android.content.pm.UserProperties.SHOW_IN_SETTINGS_WITH_PARENT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.accounts.AccountManagerInternal;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.os.Bundle;
+import android.os.UserManager;
+import android.provider.ContactsContract;
import android.test.suitebuilder.annotation.SmallTest;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.job.JobSchedulerInternal;
+
import junit.framework.TestCase;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
/**
* Tests for SyncManager.
*
@@ -33,6 +61,43 @@
final String KEY_1 = "key_1";
final String KEY_2 = "key_2";
+ private SyncManager mSyncManager;
+ private Context mContext;
+
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private AccountManagerInternal mAccountManagerInternal;
+ @Mock
+ private JobSchedulerInternal mJobSchedulerInternal;
+
+ private class SyncManagerWithMockedServices extends SyncManager {
+
+ @Override
+ protected AccountManagerInternal getAccountManagerInternal() {
+ return mAccountManagerInternal;
+ }
+
+ @Override
+ protected JobSchedulerInternal getJobSchedulerInternal() {
+ return mJobSchedulerInternal;
+ }
+
+ private SyncManagerWithMockedServices(Context context, boolean factoryTest) {
+ super(context, factoryTest);
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ doNothing().when(mAccountManagerInternal).addOnAppPermissionChangeListener(any());
+ when(mJobSchedulerInternal.getSystemScheduledPendingJobs()).thenReturn(new ArrayList<>());
+ mSyncManager = new SyncManagerWithMockedServices(mContext, true);
+ }
+
public void testSyncExtrasEquals_WithNull() throws Exception {
Bundle b1 = new Bundle();
Bundle b2 = new Bundle();
@@ -140,4 +205,45 @@
final StringBuilder sb = new StringBuilder();
assertEquals(expected, SyncManager.formatDurationHMS(sb, time * 1000).toString());
}
+
+ private UserInfo createUserInfo(String name, int id, int groupId, int flags) {
+ final UserInfo ui = new UserInfo(id, name, flags | UserInfo.FLAG_INITIALIZED);
+ ui.profileGroupId = groupId;
+ return ui;
+ }
+
+ @NotNull
+ private UserProperties getCloneUserProperties() {
+ return new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(SHOW_IN_LAUNCHER_WITH_PARENT)
+ .setShowInSettings(SHOW_IN_SETTINGS_WITH_PARENT)
+ .setUseParentsContacts(true)
+ .setInheritDevicePolicy(INHERIT_DEVICE_POLICY_FROM_PARENT)
+ .build();
+ }
+
+ private void mockUserProperties(UserInfo primaryUserInfo, UserInfo cloneUserInfo) {
+ UserProperties cloneUserProperties = getCloneUserProperties();
+ when(mUserManager.getUserProperties(cloneUserInfo.getUserHandle()))
+ .thenReturn(cloneUserProperties);
+ // Set default user properties for primary user
+ when(mUserManager.getUserProperties(primaryUserInfo.getUserHandle()))
+ .thenReturn(new UserProperties.Builder().build());
+ }
+
+ public void testShouldDisableSync() {
+ UserInfo primaryUserInfo = createUserInfo("primary", 0 /* id */, 0 /* groupId */,
+ UserInfo.FLAG_PRIMARY | UserInfo.FLAG_ADMIN);
+ UserInfo cloneUserInfo = createUserInfo("clone", 10 /* id */, 0 /* groupId */,
+ UserInfo.FLAG_PROFILE);
+
+ mockUserProperties(primaryUserInfo, cloneUserInfo);
+
+ // Clone user accounts must have contact syncs disabled
+ assertThat(mSyncManager.shouldDisableSyncForUser(cloneUserInfo,
+ ContactsContract.AUTHORITY)).isTrue();
+ assertThat(mSyncManager.shouldDisableSyncForUser(primaryUserInfo,
+ ContactsContract.AUTHORITY)).isFalse();
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 0206839..ae36871 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -18,6 +18,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -176,7 +177,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
- assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 0.0f (minimum possible brightness) as a brightness value
float lux2 = 10.0f;
@@ -190,7 +191,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
- assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
@@ -219,7 +220,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux1));
- assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness1, mController.getAutomaticScreenBrightness(), EPSILON);
// Set up system to return 1.0f as a brightness value (brightness_max)
@@ -234,7 +235,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux2));
- assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), 0.001f);
+ assertEquals(normalizedBrightness2, mController.getAutomaticScreenBrightness(), EPSILON);
}
@Test
@@ -416,6 +417,12 @@
// ambient lux goes to 0
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 0));
assertEquals(0.0f, mController.getAmbientLux(), EPSILON);
+
+ // only the values within the horizon should be kept
+ assertArrayEquals(new float[] {10000, 10000, 0, 0, 0}, mController.getLastSensorValues(),
+ EPSILON);
+ assertArrayEquals(new long[] {4000, 4500, 5000, 5500, 6000},
+ mController.getLastSensorTimestamps());
}
@Test
@@ -487,4 +494,92 @@
0 /* adjustment */, false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
assertEquals(BRIGHTNESS_MAX_FLOAT, mController.getAutomaticScreenBrightness(), 0.0f);
}
+
+ @Test
+ public void testGetSensorReadings() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Choose values such that the ring buffer's capacity is extended and the buffer is pruned
+ int increment = 11;
+ int lux = 5000;
+ for (int i = 0; i < 1000; i++) {
+ lux += increment;
+ mClock.fastForward(increment);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment + 1);
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // Only the values within the horizon should be kept
+ assertEquals(valuesCount, sensorValues.length);
+ assertEquals(valuesCount, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = valuesCount - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+ lux -= increment;
+ sensorTimestamp -= increment;
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
+
+ @Test
+ public void testGetSensorReadingsFullBuffer() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+ int initialCapacity = 150;
+
+ // Choose values such that the ring buffer is pruned
+ int increment1 = 200;
+ int lux = 5000;
+ for (int i = 0; i < 20; i++) {
+ lux += increment1;
+ mClock.fastForward(increment1);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ int valuesCount = (int) Math.ceil((double) AMBIENT_LIGHT_HORIZON_LONG / increment1 + 1);
+
+ // Choose values such that the buffer becomes full
+ int increment2 = 1;
+ for (int i = 0; i < initialCapacity - valuesCount; i++) {
+ lux += increment2;
+ mClock.fastForward(increment2);
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, lux));
+ }
+
+ float[] sensorValues = mController.getLastSensorValues();
+ long[] sensorTimestamps = mController.getLastSensorTimestamps();
+
+ // The buffer should be full
+ assertEquals(initialCapacity, sensorValues.length);
+ assertEquals(initialCapacity, sensorTimestamps.length);
+
+ long sensorTimestamp = mClock.now();
+ for (int i = initialCapacity - 1; i >= 1; i--) {
+ assertEquals(lux, sensorValues[i], EPSILON);
+ assertEquals(sensorTimestamp, sensorTimestamps[i]);
+
+ if (i >= valuesCount) {
+ lux -= increment2;
+ sensorTimestamp -= increment2;
+ } else {
+ lux -= increment1;
+ sensorTimestamp -= increment1;
+ }
+ }
+ assertEquals(lux, sensorValues[0], EPSILON);
+ assertEquals(mClock.now() - AMBIENT_LIGHT_HORIZON_LONG, sensorTimestamps[0]);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index c2e8417f..6def7b1 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -136,28 +136,28 @@
assertNull(mInjector.mSensorListener);
assertNotNull(mInjector.mBroadcastReceiver);
assertTrue(mInjector.mIdleScheduled);
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
- mInjector.sendScreenChange(/*screen on */ false);
+ mInjector.sendScreenChange(/* screenOn= */ false);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Turn screen on while brightness mode is manual
- mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ false);
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Set brightness mode to automatic while screen is off.
- mInjector.sendScreenChange(/*screen on */ false);
- mInjector.setBrightnessMode(/* isBrightnessModeAutomatic */ true);
+ mInjector.sendScreenChange(/* screenOn= */ false);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
// Turn on screen while brightness mode is automatic.
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
@@ -188,14 +188,14 @@
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is off, update config to turn on color sampling.
- mInjector.sendScreenChange(/*screen on */ false);
+ mInjector.sendScreenChange(/* screenOn= */ false);
mTracker.setBrightnessConfiguration(buildBrightnessConfiguration(
/* collectColorSamples= */ true));
mInjector.waitForHandler();
assertFalse(mInjector.mColorSamplingEnabled);
// Pretend screen is on.
- mInjector.sendScreenChange(/*screen on */ true);
+ mInjector.sendScreenChange(/* screenOn= */ true);
assertTrue(mInjector.mColorSamplingEnabled);
mTracker.stop();
@@ -261,7 +261,7 @@
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
- mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
assertNotNull(mInjector.mSensorListener);
assertTrue(mInjector.mColorSamplingEnabled);
assertNotNull(mInjector.mDisplayListener);
@@ -272,16 +272,15 @@
mInjector.mColorSamplingEnabled = false;
mInjector.mDisplayListener = null;
// Duplicate notification
- mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ true);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ true);
// Sensor shouldn't have been registered as it was already registered.
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
- mInjector.mSensorListener = listener;
mInjector.mDisplayListener = displayListener;
mInjector.mColorSamplingEnabled = true;
- mInjector.setBrightnessMode(/*isBrightnessModeAutomatic*/ false);
+ mInjector.setBrightnessMode(/* isBrightnessModeAutomatic= */ false);
assertNull(mInjector.mSensorListener);
assertFalse(mInjector.mColorSamplingEnabled);
assertNull(mInjector.mDisplayListener);
@@ -301,19 +300,21 @@
final String displayId = "1234";
startTracker(mTracker);
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
+ final long sensorTime = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
- notifyBrightnessChanged(mTracker, brightness, displayId);
+ final long currentTime = mInjector.currentTimeMillis();
+ notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1.0f},
+ new long[] {sensorTime});
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
mTracker.stop();
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
- assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
+ assertEquals(currentTime, event.timeStamp);
assertEquals(displayId, event.uniqueDisplayId);
assertEquals(1, event.luxValues.length);
assertEquals(1.0f, event.luxValues[0], FLOAT_DELTA);
- assertEquals(mInjector.currentTimeMillis() - TimeUnit.SECONDS.toMillis(2),
+ assertEquals(currentTime - TimeUnit.SECONDS.toMillis(2),
event.luxTimestamps[0]);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(DEFAULT_INITIAL_BRIGHTNESS, event.lastBrightness, FLOAT_DELTA);
@@ -339,9 +340,9 @@
startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 60));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
- final long sensorTime = mInjector.currentTimeMillis();
- notifyBrightnessChanged(mTracker, brightness, displayId);
+ final long currentTime = mInjector.currentTimeMillis();
+ notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f},
+ new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())});
List<BrightnessChangeEvent> eventsNoPackage
= mTracker.getEvents(0, false).getList();
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
@@ -349,10 +350,10 @@
assertEquals(1, events.size());
BrightnessChangeEvent event = events.get(0);
- assertEquals(event.timeStamp, mInjector.currentTimeMillis());
+ assertEquals(event.timeStamp, currentTime);
assertEquals(displayId, event.uniqueDisplayId);
- assertArrayEquals(new float[] {1000.0f}, event.luxValues, 0.01f);
- assertArrayEquals(new long[] {sensorTime}, event.luxTimestamps);
+ assertArrayEquals(new float[] {1000.0f}, event.luxValues, FLOAT_DELTA);
+ assertArrayEquals(new long[] {currentTime}, event.luxTimestamps);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(initialBrightness, event.lastBrightness, FLOAT_DELTA);
assertEquals(0.5, event.batteryLevel, FLOAT_DELTA);
@@ -374,13 +375,12 @@
public void testIgnoreAutomaticBrightnessChange() {
final int initialBrightness = 30;
startTracker(mTracker, initialBrightness, DEFAULT_COLOR_SAMPLING_ENABLED);
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
final int systemUpdatedBrightness = 20;
- notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/,
- 0.5f /*powerBrightnessFactor(*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
+ notifyBrightnessChanged(mTracker, systemUpdatedBrightness, /* userInitiated= */ false,
+ /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID);
List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
// No events because we filtered out our change.
assertEquals(0, events.size());
@@ -408,10 +408,8 @@
@Test
public void testLimitedBufferSize() {
startTracker(mTracker);
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
for (int brightness = 0; brightness <= 255; ++brightness) {
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1.0f));
mInjector.incrementTime(TimeUnit.SECONDS.toNanos(1));
notifyBrightnessChanged(mTracker, brightness);
}
@@ -427,33 +425,6 @@
}
@Test
- public void testLimitedSensorEvents() {
- final int brightness = 20;
-
- startTracker(mTracker);
- // 20 Sensor events 1 second apart.
- for (int i = 0; i < 20; ++i) {
- mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(i + 1.0f));
- }
- notifyBrightnessChanged(mTracker, 20);
- List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
- mTracker.stop();
-
- assertEquals(1, events.size());
- BrightnessChangeEvent event = events.get(0);
- assertEquals(mInjector.currentTimeMillis(), event.timeStamp);
-
- // 12 sensor events, 11 for 0->10 seconds + 1 previous event.
- assertEquals(12, event.luxValues.length);
- for (int i = 0; i < 12; ++i) {
- assertEquals(event.luxTimestamps[11 - i],
- mInjector.currentTimeMillis() - i * TimeUnit.SECONDS.toMillis(1));
- }
- assertEquals(brightness, event.brightness, FLOAT_DELTA);
- }
-
- @Test
public void testReadEvents() throws Exception {
BrightnessTracker tracker = new BrightnessTracker(InstrumentationRegistry.getContext(),
mInjector);
@@ -607,15 +578,16 @@
startTracker(mTracker);
mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
batteryChangeEvent(30, 100));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
- final long firstSensorTime = mInjector.currentTimeMillis();
+ final long elapsedTime1 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+ final long currentTime1 = mInjector.currentTimeMillis();
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
- final long secondSensorTime = mInjector.currentTimeMillis();
+ final long elapsedTime2 = TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos());
+ final long currentTime2 = mInjector.currentTimeMillis();
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(3));
- notifyBrightnessChanged(mTracker, brightness, true /*userInitiated*/,
- 0.5f /*powerBrightnessFactor*/, true /*hasUserBrightnessPoints*/,
- false /*isDefaultBrightnessConfig*/, displayId);
+ notifyBrightnessChanged(mTracker, brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 0.5f, /* isUserSetBrightness= */ true,
+ /* isDefaultBrightnessConfig= */ false, displayId, new float[] {2000.0f, 3000.0f},
+ new long[] {elapsedTime1, elapsedTime2});
ByteArrayOutputStream baos = new ByteArrayOutputStream();
mTracker.writeEventsLocked(baos);
mTracker.stop();
@@ -631,7 +603,7 @@
BrightnessChangeEvent event = events.get(0);
assertEquals(displayId, event.uniqueDisplayId);
assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
- assertArrayEquals(new long[] {firstSensorTime, secondSensorTime}, event.luxTimestamps);
+ assertArrayEquals(new long[] {currentTime1, currentTime2}, event.luxTimestamps);
assertEquals(brightness, event.brightness, FLOAT_DELTA);
assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
assertTrue(event.nightMode);
@@ -647,53 +619,6 @@
}
@Test
- public void testWritePrunesOldEvents() throws Exception {
- final int brightness = 20;
-
- mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
- mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
-
- mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1);
- mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40);
-
- startTracker(mTracker);
- mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
- batteryChangeEvent(30, 100));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
- mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
- final long sensorTime = mInjector.currentTimeMillis();
- notifyBrightnessChanged(mTracker, brightness);
-
- // 31 days later
- mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
- mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
- notifyBrightnessChanged(mTracker, brightness);
- final long eventTime = mInjector.currentTimeMillis();
-
- List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList();
- assertEquals(2, events.size());
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- mTracker.writeEventsLocked(baos);
- events = mTracker.getEvents(0, true).getList();
- mTracker.stop();
-
- assertEquals(1, events.size());
- BrightnessChangeEvent event = events.get(0);
- assertEquals(eventTime, event.timeStamp);
-
- // We will keep one of the old sensor events because we keep 1 event outside the window.
- assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, FLOAT_DELTA);
- assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
- assertEquals(brightness, event.brightness, FLOAT_DELTA);
- assertEquals(0.3, event.batteryLevel, FLOAT_DELTA);
- assertTrue(event.nightMode);
- assertTrue(event.reduceBrightColors);
- assertEquals(3339, event.colorTemperature);
- }
-
- @Test
public void testParcelUnParcel() {
Parcel parcel = Parcel.obtain();
BrightnessChangeEvent.Builder builder = new BrightnessChangeEvent.Builder();
@@ -796,9 +721,10 @@
// Send an event.
long eventTime = mInjector.currentTimeMillis();
- mTracker.notifyBrightnessChanged(brightness, true /*userInitiated*/,
- 1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/, DEFAULT_DISPLAY_ID);
+ mTracker.notifyBrightnessChanged(brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, DEFAULT_DISPLAY_ID, new float[10],
+ new long[10]);
// Time passes before handler can run.
mInjector.incrementTime(TimeUnit.SECONDS.toMillis(2));
@@ -890,20 +816,33 @@
public void testOnlyOneReceiverRegistered() {
assertNull(mInjector.mLightSensor);
assertNull(mInjector.mSensorListener);
+ assertNull(mInjector.mContentObserver);
+ assertNull(mInjector.mBroadcastReceiver);
+ assertFalse(mInjector.mIdleScheduled);
startTracker(mTracker, 0.3f, false);
assertNotNull(mInjector.mLightSensor);
assertNotNull(mInjector.mSensorListener);
+ assertNotNull(mInjector.mContentObserver);
+ assertNotNull(mInjector.mBroadcastReceiver);
+ assertTrue(mInjector.mIdleScheduled);
Sensor registeredLightSensor = mInjector.mLightSensor;
SensorEventListener registeredSensorListener = mInjector.mSensorListener;
+ ContentObserver registeredContentObserver = mInjector.mContentObserver;
+ BroadcastReceiver registeredBroadcastReceiver = mInjector.mBroadcastReceiver;
mTracker.start(0.3f);
assertSame(registeredLightSensor, mInjector.mLightSensor);
assertSame(registeredSensorListener, mInjector.mSensorListener);
+ assertSame(registeredContentObserver, mInjector.mContentObserver);
+ assertSame(registeredBroadcastReceiver, mInjector.mBroadcastReceiver);
mTracker.stop();
assertNull(mInjector.mLightSensor);
assertNull(mInjector.mSensorListener);
+ assertNull(mInjector.mContentObserver);
+ assertNull(mInjector.mBroadcastReceiver);
+ assertFalse(mInjector.mIdleScheduled);
// mInjector asserts that we aren't removing a null receiver
mTracker.stop();
@@ -954,23 +893,41 @@
private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
String displayId) {
- notifyBrightnessChanged(tracker, brightness, true /*userInitiated*/,
- 1.0f /*powerBrightnessFactor*/, false /*isUserSetBrightness*/,
- false /*isDefaultBrightnessConfig*/, displayId);
+ notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, displayId, new float[10], new long[10]);
+ }
+
+ private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+ String displayId, float[] luxValues, long[] luxTimestamps) {
+ notifyBrightnessChanged(tracker, brightness, /* userInitiated= */ true,
+ /* powerBrightnessFactor= */ 1.0f, /* isUserSetBrightness= */ false,
+ /* isDefaultBrightnessConfig= */ false, displayId, luxValues, luxTimestamps);
}
private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
boolean isDefaultBrightnessConfig, String displayId) {
tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
- isUserSetBrightness, isDefaultBrightnessConfig, displayId);
+ isUserSetBrightness, isDefaultBrightnessConfig, displayId, new float[10],
+ new long[10]);
+ mInjector.waitForHandler();
+ }
+
+ private void notifyBrightnessChanged(BrightnessTracker tracker, float brightness,
+ boolean userInitiated, float powerBrightnessFactor, boolean isUserSetBrightness,
+ boolean isDefaultBrightnessConfig, String displayId, float[] luxValues,
+ long[] luxTimestamps) {
+ tracker.notifyBrightnessChanged(brightness, userInitiated, powerBrightnessFactor,
+ isUserSetBrightness, isDefaultBrightnessConfig, displayId, luxValues,
+ luxTimestamps);
mInjector.waitForHandler();
}
private BrightnessConfiguration buildBrightnessConfiguration(boolean collectColorSamples) {
BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(
- /* lux = */ new float[] {0f, 10f, 100f},
- /* nits = */ new float[] {1f, 90f, 100f});
+ /* lux= */ new float[] {0f, 10f, 100f},
+ /* nits= */ new float[] {1f, 90f, 100f});
builder.setShouldCollectColorSamples(collectColorSamples);
return builder.build();
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
new file mode 100644
index 0000000..bcae50e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.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.server.display;
+
+
+import static org.junit.Assert.assertEquals;
+
+import android.view.DisplayAddress;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.layout.DisplayIdProducer;
+import com.android.server.display.layout.Layout;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+@SmallTest
+public class DeviceStateToLayoutMapTest {
+ private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
+
+ @Mock DisplayIdProducer mDisplayIdProducerMock;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+
+ Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
+
+ setupDeviceStateToLayoutMap();
+ }
+
+ //////////////////
+ // Test Methods //
+ //////////////////
+
+ @Test
+ public void testInitialState() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ false,
+ /* isEnabled= */ false, mDisplayIdProducerMock);
+ assertEquals(testLayout, configLayout);
+ }
+
+ @Test
+ public void testSwitchedState() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(1);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(78910L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(123456L), /* isDefault= */ false,
+ /* isEnabled= */ false, mDisplayIdProducerMock);
+
+ assertEquals(testLayout, configLayout);
+ }
+
+ @Test
+ public void testConcurrentState() {
+ Layout configLayout = mDeviceStateToLayoutMap.get(2);
+
+ Layout testLayout = new Layout();
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(345L), /* isDefault= */ true,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+ testLayout.createDisplayLocked(
+ DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false,
+ /* isEnabled= */ true, mDisplayIdProducerMock);
+
+ assertEquals(testLayout, configLayout);
+ }
+
+ ////////////////////
+ // Helper Methods //
+ ////////////////////
+
+ private void setupDeviceStateToLayoutMap() throws IOException {
+ Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+ tempFile.toFile());
+ }
+
+ private String getContent() {
+ return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<layouts>\n"
+ + "<layout>\n"
+ + "<state>0</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>123456</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"false\">\n"
+ + "<address>78910</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+
+ + "<layout>\n"
+ + "<state>1</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>78910</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"false\">\n"
+ + "<address>123456</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+
+ + "<layout>\n"
+ + "<state>2</state> \n"
+ + "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+ + "<address>345</address>\n"
+ + "</display>\n"
+ + "<display enabled=\"true\">\n"
+ + "<address>678</address>\n"
+ + "</display>\n"
+ + "</layout>\n"
+ + "</layouts>\n";
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 109abd0..ca8e45a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -16,7 +16,9 @@
package com.android.server.display;
+import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
@@ -219,6 +221,7 @@
SurfaceControl.DisplayMode displayMode = new SurfaceControl.DisplayMode();
displayMode.width = 100;
displayMode.height = 200;
+ displayMode.supportedHdrTypes = new int[]{1, 2};
dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
when(mSurfaceControlProxy.getDynamicDisplayInfo(mMockDisplayToken))
.thenReturn(dynamicDisplayMode);
@@ -723,9 +726,6 @@
registerDefaultDisplays(displayManager);
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_DENIED);
-
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
.thenReturn(true);
@@ -779,9 +779,6 @@
registerDefaultDisplays(displayManager);
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
- .thenReturn(PackageManager.PERMISSION_DENIED);
-
IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
.thenReturn(true);
@@ -791,7 +788,7 @@
// virtual device.
final VirtualDisplayConfig.Builder builder1 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
- .setUniqueId("uniqueId --- device display group 1");
+ .setUniqueId("uniqueId --- device display group");
int displayId1 =
localService.createVirtualDisplay(
@@ -807,7 +804,7 @@
final VirtualDisplayConfig.Builder builder2 =
new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
- .setUniqueId("uniqueId --- device display group 1");
+ .setUniqueId("uniqueId --- own display group");
int displayId2 =
localService.createVirtualDisplay(
@@ -826,6 +823,99 @@
}
@Test
+ public void displaysInDeviceOrOwnDisplayGroupShouldPreserveAlwaysUnlockedFlag()
+ throws Exception {
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerInternal localService = displayManager.new LocalService();
+
+ registerDefaultDisplays(displayManager);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+
+ IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
+ when(mMockVirtualDeviceManagerInternal.isValidVirtualDevice(virtualDevice))
+ .thenReturn(true);
+ when(virtualDevice.getDeviceId()).thenReturn(1);
+
+ // Allow an ALWAYS_UNLOCKED display to be created.
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ when(mContext.checkCallingPermission(ADD_ALWAYS_UNLOCKED_DISPLAY))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ // Create a virtual display in a device display group.
+ final VirtualDisplayConfig deviceDisplayGroupDisplayConfig =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- device display group 1")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .build();
+
+ int deviceDisplayGroupDisplayId =
+ localService.createVirtualDisplay(
+ deviceDisplayGroupDisplayConfig,
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // Check that FLAG_ALWAYS_UNLOCKED is set.
+ assertNotEquals(
+ "FLAG_ALWAYS_UNLOCKED should be set for displays created in a device display"
+ + " group.",
+ (displayManager.getDisplayDeviceInfoInternal(deviceDisplayGroupDisplayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+ 0);
+
+ // Create a virtual display in its own display group.
+ final VirtualDisplayConfig ownDisplayGroupConfig =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- own display group 1")
+ .setFlags(
+ VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
+ | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP)
+ .build();
+
+ int ownDisplayGroupDisplayId =
+ localService.createVirtualDisplay(
+ ownDisplayGroupConfig,
+ mMockAppToken /* callback */,
+ virtualDevice /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // Check that FLAG_ALWAYS_UNLOCKED is set.
+ assertNotEquals(
+ "FLAG_ALWAYS_UNLOCKED should be set for displays created in their own display"
+ + " group.",
+ (displayManager.getDisplayDeviceInfoInternal(ownDisplayGroupDisplayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+ 0);
+
+ // Create a virtual display in a device display group.
+ final VirtualDisplayConfig defaultDisplayGroupConfig =
+ new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
+ .setUniqueId("uniqueId --- default display group 1")
+ .setFlags(VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED)
+ .build();
+
+ int defaultDisplayGroupDisplayId =
+ localService.createVirtualDisplay(
+ defaultDisplayGroupConfig,
+ mMockAppToken /* callback */,
+ null /* virtualDeviceToken */,
+ mock(DisplayWindowPolicyController.class),
+ PACKAGE_NAME);
+
+ // Check that FLAG_ALWAYS_UNLOCKED is not set.
+ assertEquals(
+ "FLAG_ALWAYS_UNLOCKED should not be set for displays created in the default"
+ + " display group.",
+ (displayManager.getDisplayDeviceInfoInternal(defaultDisplayGroupDisplayId).flags
+ & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED),
+ 0);
+ }
+
+ @Test
public void testGetDisplayIdToMirror() throws Exception {
DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
registerDefaultDisplays(displayManager);
@@ -1163,6 +1253,76 @@
}
/**
+ * Tests that there is a display change notification if the render frame rate is updated
+ */
+ @Test
+ public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+ FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+ displayManagerBinderService, displayDevice);
+
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ assertTrue(callback.mDisplayChangedCalled);
+ callback.clear();
+
+ updateRenderFrameRate(displayManager, displayDevice, 30f);
+ assertFalse(callback.mDisplayChangedCalled);
+
+ updateRenderFrameRate(displayManager, displayDevice, 20f);
+ assertTrue(callback.mDisplayChangedCalled);
+ callback.clear();
+ }
+
+ /**
+ * Tests that the DisplayInfo is updated correctly with a render frame rate
+ */
+ @Test
+ public void testDisplayInfoRenderFrameRate() throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f, 30f, 20f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateRenderFrameRate(displayManager, displayDevice, 20f);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+ }
+
+ /**
+ * Tests that the mode reflects the render frame rate is in compat mode
+ */
+ @Test
+ @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+ testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+ }
+
+ /**
+ * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+ */
+ @Test
+ @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+ public void testDisplayInfoRenderFrameRateMode() throws Exception {
+ testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
+ }
+
+ /**
* Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
*/
@Test
@@ -1382,6 +1542,34 @@
assertEquals(expectedMode, displayInfo.getMode());
}
+ private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+ throws Exception {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mShortMockedInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f, 30f, 20f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateRenderFrameRate(displayManager, displayDevice, 20f);
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+ Display.Mode expectedMode;
+ if (compatChangeEnabled) {
+ expectedMode = new Display.Mode(1, 100, 200, 60f);
+ } else {
+ expectedMode = new Display.Mode(3, 100, 200, 20f);
+ }
+ assertEquals(expectedMode, displayInfo.getMode());
+ }
+
private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
DisplayManagerService displayManager =
new DisplayManagerService(mContext, mBasicInjector);
@@ -1451,6 +1639,15 @@
updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
}
+ private void updateRenderFrameRate(DisplayManagerService displayManager,
+ FakeDisplayDevice displayDevice,
+ float renderFrameRate) {
+ DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+ displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+ displayDeviceInfo.renderFrameRate = renderFrameRate;
+ updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+ }
+
private void updateModeId(DisplayManagerService displayManager,
FakeDisplayDevice displayDevice,
int modeId) {
@@ -1490,6 +1687,7 @@
new Display.Mode(i + 1, width, height, refreshRates[i]);
}
displayDeviceInfo.modeId = 1;
+ displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
displayDeviceInfo.width = width;
displayDeviceInfo.height = height;
final Rect zeroRect = new Rect();
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 246945c..c7caa43 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -54,6 +54,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.display.layout.DisplayIdProducer;
import com.android.server.display.layout.Layout;
import org.junit.Before;
@@ -76,6 +77,7 @@
private static int sUniqueTestDisplayId = 0;
private static final int DEVICE_STATE_CLOSED = 0;
private static final int DEVICE_STATE_OPEN = 2;
+ private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1;
private DisplayDeviceRepository mDisplayDeviceRepo;
private LogicalDisplayMapper mLogicalDisplayMapper;
@@ -83,12 +85,16 @@
private Handler mHandler;
private PowerManager mPowerManager;
+ private final DisplayIdProducer mIdProducer = (isDefault) ->
+ isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
+
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@Mock Resources mResourcesMock;
@Mock IPowerManager mIPowerManagerMock;
@Mock IThermalService mIThermalServiceMock;
- @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy = new DeviceStateToLayoutMap();
+ @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
+ new DeviceStateToLayoutMap(mIdProducer);
@Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
@@ -519,13 +525,17 @@
DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
Layout layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, true, true);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, false, false);
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+ true, true, mIdProducer);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+ false, false, mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout);
layout = new Layout();
- layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address, false, false);
- layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, true, true);
+ layout.createDisplayLocked(device1.getDisplayDeviceInfoLocked().address,
+ false, false, mIdProducer);
+ layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address,
+ true, true, mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(layout);
when(mDeviceStateToLayoutMapSpy.get(2)).thenReturn(layout);
@@ -580,15 +590,18 @@
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressOne,
/* isDefault= */ true,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
threeDevicesEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
.thenReturn(threeDevicesEnabledLayout);
@@ -622,15 +635,18 @@
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressOne,
/* isDefault= */ true,
- /* isEnabled= */ true);
+ /* isEnabled= */ true,
+ mIdProducer);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressTwo,
/* isDefault= */ false,
- /* isEnabled= */ false);
+ /* isEnabled= */ false,
+ mIdProducer);
oneDeviceEnabledLayout.createDisplayLocked(
displayAddressThree,
/* isDefault= */ false,
- /* isEnabled= */ false);
+ /* isEnabled= */ false,
+ mIdProducer);
when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 3b0a22f..35a677e 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -344,6 +344,40 @@
assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
}
+ @Test
+ public void testBrightnessInitialisesWithInvalidFloat() {
+ final String uniqueDisplayId = "test:123";
+ DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+ @Override
+ public boolean hasStableUniqueId() {
+ return true;
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ return null;
+ }
+ };
+
+ // Set any value which initialises Display state
+ float refreshRate = 85.3f;
+ mDataStore.loadIfNeeded();
+ mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+ }
+
+
public class TestInjector extends PersistentDataStore.Injector {
private InputStream mReadStream;
private OutputStream mWriteStream;
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
index a5d7a10..dcf217c 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -29,6 +29,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
+import com.android.server.display.brightness.strategy.BoostBrightnessStrategy;
import com.android.server.display.brightness.strategy.DozeBrightnessStrategy;
import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy;
import com.android.server.display.brightness.strategy.OverrideBrightnessStrategy;
@@ -56,6 +57,8 @@
@Mock
private TemporaryBrightnessStrategy mTemporaryBrightnessStrategy;
@Mock
+ private BoostBrightnessStrategy mBoostBrightnessStrategy;
+ @Mock
private InvalidBrightnessStrategy mInvalidBrightnessStrategy;
@Mock
private Context mContext;
@@ -92,6 +95,11 @@
}
@Override
+ BoostBrightnessStrategy getBoostBrightnessStrategy() {
+ return mBoostBrightnessStrategy;
+ }
+
+ @Override
InvalidBrightnessStrategy getInvalidBrightnessStrategy() {
return mInvalidBrightnessStrategy;
}
@@ -140,6 +148,17 @@
}
@Test
+ public void selectStrategySelectsBoostStrategyWhenValid() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.boostScreenBrightness = true;
+ displayPowerRequest.screenBrightnessOverride = Float.NaN;
+ when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ assertEquals(mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
+ Display.STATE_ON), mBoostBrightnessStrategy);
+ }
+
+ @Test
public void selectStrategySelectsInvalidStrategyWhenNoStrategyIsValid() {
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
DisplayManagerInternal.DisplayPowerRequest.class);
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
new file mode 100644
index 0000000..431a239
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+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 class BoostBrightnessStrategyTest {
+ private BoostBrightnessStrategy mBoostBrightnessStrategy;
+
+ @Before
+ public void before() {
+ mBoostBrightnessStrategy = new BoostBrightnessStrategy();
+ }
+
+ @Test
+ public void updateBrightnessWorksAsExpectedWhenBoostBrightnessIsRequested() {
+ DisplayManagerInternal.DisplayPowerRequest
+ displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest();
+ displayPowerRequest.boostScreenBrightness = true;
+ BrightnessReason brightnessReason = new BrightnessReason();
+ brightnessReason.setReason(BrightnessReason.REASON_BOOST);
+ DisplayBrightnessState expectedDisplayBrightnessState =
+ new DisplayBrightnessState.Builder()
+ .setBrightness(PowerManager.BRIGHTNESS_MAX)
+ .setBrightnessReason(brightnessReason)
+ .setSdrBrightness(PowerManager.BRIGHTNESS_MAX)
+ .build();
+ DisplayBrightnessState updatedDisplayBrightnessState =
+ mBoostBrightnessStrategy.updateBrightness(displayPowerRequest);
+ assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 09cd47a..2cb46da 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -24,6 +24,8 @@
import android.content.Context;
import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
import android.hardware.tv.cec.V1_0.SendMessageResult;
import android.media.AudioManager;
import android.os.Looper;
@@ -52,6 +54,7 @@
private Context mContextSpy;
private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
private FakePowerManagerWrapper mPowerManager;
+ private TestCallback mCallback;
private ArcTerminationActionFromAvr mAction;
private FakeNativeWrapper mNativeWrapper;
@@ -112,7 +115,9 @@
}
};
mHdmiCecLocalDeviceAudioSystem.init();
- mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+ mCallback = new TestCallback();
+ mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem,
+ mCallback);
mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -121,6 +126,20 @@
mTestLooper.dispatchAll();
}
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+
+ private int getResult() {
+ assertThat(mCallbackResult.size()).isEqualTo(1);
+ return mCallbackResult.get(0);
+ }
+ }
+
@Test
public void testSendMessage_sendFailed() {
mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
@@ -133,6 +152,7 @@
assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
@Test
@@ -149,6 +169,7 @@
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
}
@Test
@@ -167,5 +188,28 @@
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void testReportArcTerminated_featureAbort() {
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+ Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+ HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV,
+ Constants.ADDR_AUDIO_SYSTEM,
+ Constants.MESSAGE_TERMINATE_ARC,
+ Constants.ABORT_REFUSED);
+
+ mNativeWrapper.onCecMessage(arcTerminatedResponse);
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+ assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 7c6c990..de2c218 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -535,6 +535,25 @@
}
@Test
+ public void handleRequestArcTerminate_callbackIsPreserved() throws Exception {
+ TestCallback callback = new TestCallback();
+
+ mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+ mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+ new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback));
+
+ HdmiCecMessage message =
+ HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+ .isEqualTo(Constants.HANDLED);
+
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(
+ ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback);
+ }
+
+ @Test
public void handleRequestArcInit_arcIsNotSupported() throws Exception {
HdmiCecMessage message =
HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
@@ -880,4 +899,13 @@
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
systemAudioModeRequest_fromAudioSystem);
}
+
+ private static class TestCallback extends IHdmiControlCallback.Stub {
+ private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+ @Override
+ public void onComplete(int result) {
+ mCallbackResult.add(result);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index ef2b212..49a0a9a52 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -1036,6 +1036,7 @@
@Test
public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
// Initialize the local devices excluding the audio system.
mHdmiControlServiceSpy.clearCecLocalDevices();
mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1053,6 +1054,7 @@
@Test
public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+ mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
// Initialize the local devices excluding the audio system.
mHdmiControlServiceSpy.clearCecLocalDevices();
mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1073,6 +1075,10 @@
HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
HdmiControlManager.SOUNDBAR_MODE_DISABLED);
mTestLooper.dispatchAll();
+
+ // Wait for ArcTerminationActionFromAvr timeout for the logical address allocation to start.
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
}
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 dc47b5e..0589b3a 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,6 +8,7 @@
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;
@@ -22,6 +23,7 @@
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.test.RenamingDelegatingContext;
+import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -38,9 +40,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.time.Clock;
import java.time.ZoneOffset;
-import java.util.Iterator;
/**
* Test reading and writing correctly from file.
@@ -93,11 +95,147 @@
mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L);
}
+ private void setUseSplitFiles(boolean useSplitFiles) throws Exception {
+ mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles);
+ waitForPendingIo();
+ }
+
private void waitForPendingIo() throws Exception {
assertTrue("Timed out waiting for persistence I/O to complete",
mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L));
}
+ /** Test that we properly remove the last job of an app from the persisted file. */
+ @Test
+ public void testRemovingLastJob_singleFile() throws Exception {
+ setUseSplitFiles(false);
+ runRemovingLastJob();
+ }
+
+ /** Test that we properly remove the last job of an app from the persisted file. */
+ @Test
+ public void testRemovingLastJob_splitFiles() throws Exception {
+ setUseSplitFiles(true);
+ runRemovingLastJob();
+ }
+
+ private void runRemovingLastJob() throws Exception {
+ final JobInfo task1 = new Builder(8, mComponent)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(10000L)
+ .setRequiresCharging(true)
+ .setPersisted(true)
+ .build();
+ final JobInfo task2 = new Builder(12, mComponent)
+ .setMinimumLatency(5000L)
+ .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(30000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+ // Remove 1 job
+ mTaskStoreUnderTest.remove(JobStatus1, true);
+ waitForPendingIo();
+ JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+ JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+
+ assertJobsEqual(JobStatus2, loaded);
+ assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2));
+
+ // Remove 2nd job
+ mTaskStoreUnderTest.remove(JobStatus2, true);
+ waitForPendingIo();
+ jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+ }
+
+ /** Test that we properly clear the persisted file when all jobs are dropped. */
+ @Test
+ public void testClearJobs_singleFile() throws Exception {
+ setUseSplitFiles(false);
+ runClearJobs();
+ }
+
+ /** Test that we properly clear the persisted file when all jobs are dropped. */
+ @Test
+ public void testClearJobs_splitFiles() throws Exception {
+ setUseSplitFiles(true);
+ runClearJobs();
+ }
+
+ private void runClearJobs() throws Exception {
+ final JobInfo task1 = new Builder(8, mComponent)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(10000L)
+ .setRequiresCharging(true)
+ .setPersisted(true)
+ .build();
+ final JobInfo task2 = new Builder(12, mComponent)
+ .setMinimumLatency(5000L)
+ .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setOverrideDeadline(30000L)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ final int uid1 = SOME_UID;
+ final int uid2 = uid1 + 1;
+ final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+ final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+ runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+ // Remove all jobs
+ mTaskStoreUnderTest.clear();
+ waitForPendingIo();
+ JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+ }
+
+ @Test
+ public void testExtractUidFromJobFileName() {
+ File file = new File(mTestContext.getFilesDir(), "randomName");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), "jobs.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), ".xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), "1000.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), "10000");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+ assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+ assertEquals(1, JobStore.extractUidFromJobFileName(file));
+
+ file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+ assertEquals(101023, JobStore.extractUidFromJobFileName(file));
+ }
+
@Test
public void testStringToIntArrayAndIntArrayToString() {
final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 };
@@ -144,13 +282,13 @@
@Test
public void testWritingTwoJobsToDisk_singleFile() throws Exception {
- mTaskStoreUnderTest.setUseSplitFiles(false);
+ setUseSplitFiles(false);
runWritingTwoJobsToDisk();
}
@Test
public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
- mTaskStoreUnderTest.setUseSplitFiles(true);
+ setUseSplitFiles(true);
runWritingTwoJobsToDisk();
}
@@ -172,28 +310,44 @@
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);
+
+ runWritingJobsToDisk(taskStatus1, taskStatus2);
+ }
+
+ private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception {
+ ArraySet<JobStatus> expectedJobs = new ArraySet<>();
+ for (JobStatus jobStatus : jobStatuses) {
+ mTaskStoreUnderTest.add(jobStatus);
+ expectedJobs.add(jobStatus);
+ }
waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
- assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
- Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
- JobStatus loaded1 = it.next();
- JobStatus loaded2 = it.next();
+ assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size());
+ int count = 0;
+ final int expectedCount = expectedJobs.size();
+ for (JobStatus loaded : jobStatusSet.getAllJobs()) {
+ count++;
+ for (int i = 0; i < expectedJobs.size(); ++i) {
+ JobStatus expected = expectedJobs.valueAt(i);
- // Reverse them so we know which comparison to make.
- if (loaded1.getJobId() != 8) {
- JobStatus tmp = loaded1;
- loaded1 = loaded2;
- loaded2 = tmp;
+ try {
+ assertJobsEqual(expected, loaded);
+ expectedJobs.remove(expected);
+ break;
+ } catch (AssertionError e) {
+ // Not equal. Move along.
+ }
+ }
}
-
- assertJobsEqual(taskStatus1, loaded1);
- assertJobsEqual(taskStatus2, loaded2);
- assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
- assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
+ assertEquals("Loaded more jobs than expected", expectedCount, count);
+ if (expectedJobs.size() > 0) {
+ fail("Not all expected jobs were restored");
+ }
+ for (JobStatus jobStatus : jobStatuses) {
+ assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus));
+ }
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index c934e65..55ab4a0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -33,6 +33,7 @@
import android.app.admin.DeviceStateCache;
import android.app.trust.TrustManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.authsecret.V1_0.IAuthSecret;
@@ -41,14 +42,18 @@
import android.os.FileUtils;
import android.os.IProgressListener;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
@@ -59,6 +64,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -106,7 +112,8 @@
FingerprintManager mFingerprintManager;
FaceManager mFaceManager;
PackageManager mPackageManager;
- FakeSettings mSettings;
+ @Rule
+ public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Before
public void setUp_baseServices() throws Exception {
@@ -126,7 +133,6 @@
mFingerprintManager = mock(FingerprintManager.class);
mFaceManager = mock(FaceManager.class);
mPackageManager = mock(PackageManager.class);
- mSettings = new FakeSettings();
LocalServices.removeServiceForTest(LockSettingsInternal.class);
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -134,12 +140,13 @@
LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
- mContext = new MockLockSettingsContext(InstrumentationRegistry.getContext(), mUserManager,
- mNotificationManager, mDevicePolicyManager, mock(StorageManager.class),
- mock(TrustManager.class), mock(KeyguardManager.class), mFingerprintManager,
- mFaceManager, mPackageManager);
+ final Context origContext = InstrumentationRegistry.getContext();
+ mContext = new MockLockSettingsContext(origContext,
+ mSettingsRule.mockContentResolver(origContext), mUserManager, mNotificationManager,
+ mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
+ mock(KeyguardManager.class), mFingerprintManager, mFaceManager, mPackageManager);
mStorage = new LockSettingsStorageTestable(mContext,
- new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+ new File(origContext.getFilesDir(), "locksettings"));
File storageDir = mStorage.mStorageDir;
if (storageDir.exists()) {
FileUtils.deleteContents(storageDir);
@@ -153,7 +160,7 @@
mService = new LockSettingsServiceTestable(mContext, mStorage,
mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
- mUserManagerInternal, mDeviceStateCache, mSettings);
+ mUserManagerInternal, mDeviceStateCache);
mService.mHasSecureLockScreen = true;
when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
@@ -186,10 +193,25 @@
mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
- mSettings.setDeviceProvisioned(true);
+ setDeviceProvisioned(true);
mLocalService = LocalServices.getService(LockSettingsInternal.class);
}
+ protected void setDeviceProvisioned(boolean provisioned) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, provisioned ? 1 : 0);
+ }
+
+ protected void setUserSetupComplete(boolean complete) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, complete ? 1 : 0, UserHandle.USER_SYSTEM);
+ }
+
+ protected void setSecureFrpMode(boolean secure) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM);
+ }
+
private UserInfo installChildProfile(int profileId) {
final UserInfo userInfo = new UserInfo(
profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java b/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
deleted file mode 100644
index 2bcd653..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/FakeSettings.java
+++ /dev/null
@@ -1,60 +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.server.locksettings;
-
-import android.content.ContentResolver;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-public class FakeSettings {
-
- private int mDeviceProvisioned;
- private int mSecureFrpMode;
- private int mUserSetupComplete;
-
- public void setDeviceProvisioned(boolean provisioned) {
- mDeviceProvisioned = provisioned ? 1 : 0;
- }
-
- public void setSecureFrpMode(boolean secure) {
- mSecureFrpMode = secure ? 1 : 0;
- }
-
- public void setUserSetupComplete(boolean complete) {
- mUserSetupComplete = complete ? 1 : 0;
- }
-
- public int globalGetInt(String keyName) {
- switch (keyName) {
- case Settings.Global.DEVICE_PROVISIONED:
- return mDeviceProvisioned;
- default:
- throw new IllegalArgumentException("Unhandled global settings: " + keyName);
- }
- }
-
- public int secureGetInt(ContentResolver contentResolver, String keyName, int defaultValue,
- int userId) {
- if (Settings.Secure.SECURE_FRP_MODE.equals(keyName) && userId == UserHandle.USER_SYSTEM) {
- return mSecureFrpMode;
- }
- if (Settings.Secure.USER_SETUP_COMPLETE.equals(keyName)
- && userId == UserHandle.USER_SYSTEM) {
- return mUserSetupComplete;
- }
- return defaultValue;
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 85db23c..eccfa06 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -20,7 +20,6 @@
import android.app.IActivityManager;
import android.app.admin.DeviceStateCache;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.UserInfo;
import android.hardware.authsecret.V1_0.IAuthSecret;
@@ -52,14 +51,12 @@
private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private UserManagerInternal mUserManagerInternal;
private DeviceStateCache mDeviceStateCache;
- private FakeSettings mSettings;
public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
IActivityManager activityManager,
IStorageManager storageManager, SyntheticPasswordManager spManager,
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
- FakeSettings settings) {
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(context);
mLockSettingsStorage = storage;
mKeyStore = keyStore;
@@ -70,7 +67,6 @@
mRecoverableKeyStoreManager = recoverableKeyStoreManager;
mUserManagerInternal = userManagerInternal;
mDeviceStateCache = deviceStateCache;
- mSettings = settings;
}
@Override
@@ -119,18 +115,6 @@
}
@Override
- public int settingsGlobalGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue) {
- return mSettings.globalGetInt(keyName);
- }
-
- @Override
- public int settingsSecureGetInt(ContentResolver contentResolver, String keyName,
- int defaultValue, int userId) {
- return mSettings.secureGetInt(contentResolver, keyName, defaultValue, userId);
- }
-
- @Override
public UserManagerInternal getUserManagerInternal() {
return mUserManagerInternal;
}
@@ -165,11 +149,10 @@
IStorageManager storageManager, IActivityManager mActivityManager,
SyntheticPasswordManager spManager, IAuthSecret authSecretService,
FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
- UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache,
- FakeSettings settings) {
+ UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(new MockInjector(context, storage, keystore, mActivityManager,
- storageManager, spManager, gsiService,
- recoverableKeyStoreManager, userManagerInternal, deviceStateCache, settings));
+ storageManager, spManager, gsiService, recoverableKeyStoreManager,
+ userManagerInternal, deviceStateCache));
mGateKeeperService = gatekeeper;
mAuthSecretService = authSecretService;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 3f259e3..196226a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -424,8 +424,8 @@
@Test
public void testCredentialChangeNotPossibleInSecureFrpModeDuringSuw() {
- mSettings.setUserSetupComplete(false);
- mSettings.setSecureFrpMode(true);
+ setUserSetupComplete(false);
+ setSecureFrpMode(true);
try {
mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID);
fail("Password shouldn't be changeable before FRP unlock");
@@ -434,8 +434,8 @@
@Test
public void testCredentialChangePossibleInSecureFrpModeAfterSuw() {
- mSettings.setUserSetupComplete(true);
- mSettings.setSecureFrpMode(true);
+ setUserSetupComplete(true);
+ setSecureFrpMode(true);
assertTrue(mService.setLockCredential(newPassword("1234"), nonePassword(),
PRIMARY_USER_ID));
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index a663858..95d0e15 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -31,6 +31,7 @@
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.sqlite.SQLiteDatabase;
@@ -49,11 +50,14 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -78,14 +82,17 @@
public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
- LockSettingsStorageTestable mStorage;
- File mStorageDir;
-
+ private LockSettingsStorageTestable mStorage;
+ private File mStorageDir;
private File mDb;
+ @Rule
+ public FakeSettingsProviderRule mSettingsRule = FakeSettingsProvider.rule();
@Before
public void setUp() throws Exception {
- mStorageDir = new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings");
+ final Context origContext = InstrumentationRegistry.getContext();
+
+ mStorageDir = new File(origContext.getFilesDir(), "locksettings");
mDb = InstrumentationRegistry.getContext().getDatabasePath("locksettings.db");
assertTrue(mStorageDir.exists() || mStorageDir.mkdirs());
@@ -98,8 +105,8 @@
// User 3 is a profile of user 0.
when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0));
- MockLockSettingsContext context = new MockLockSettingsContext(
- InstrumentationRegistry.getContext(), mockUserManager,
+ MockLockSettingsContext context = new MockLockSettingsContext(origContext,
+ mSettingsRule.mockContentResolver(origContext), mockUserManager,
mock(NotificationManager.class), mock(DevicePolicyManager.class),
mock(StorageManager.class), mock(TrustManager.class), mock(KeyguardManager.class),
mock(FingerprintManager.class), mock(FaceManager.class),
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
index c2f94e2..fc0ca7e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockscreenFrpTest.java
@@ -44,7 +44,7 @@
@Before
public void setDeviceNotProvisioned() throws Exception {
// FRP credential can only be verified prior to provisioning
- mSettings.setDeviceProvisioned(false);
+ setDeviceProvisioned(false);
}
@Before
@@ -98,6 +98,7 @@
mService.setLockCredential(newPassword("1234"), nonePassword(), PRIMARY_USER_ID);
assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(USER_FRP));
+ setDeviceProvisioned(true);
mService.setLockCredential(nonePassword(), newPassword("1234"), PRIMARY_USER_ID);
assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(USER_FRP));
}
@@ -106,7 +107,7 @@
public void testFrpCredential_cannotVerifyAfterProvsioning() {
mService.setLockCredential(newPin("1234"), nonePassword(), PRIMARY_USER_ID);
- mSettings.setDeviceProvisioned(true);
+ setDeviceProvisioned(true);
assertEquals(VerifyCredentialResponse.RESPONSE_ERROR,
mService.verifyCredential(newPin("1234"), USER_FRP, 0 /* flags */)
.getResponseCode());
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
index efa1b04..21c367b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/MockLockSettingsContext.java
@@ -21,6 +21,7 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
@@ -35,22 +36,25 @@
public class MockLockSettingsContext extends ContextWrapper {
- private UserManager mUserManager;
- private NotificationManager mNotificationManager;
- private DevicePolicyManager mDevicePolicyManager;
- private StorageManager mStorageManager;
- private TrustManager mTrustManager;
- private KeyguardManager mKeyguardManager;
- private FingerprintManager mFingerprintManager;
- private FaceManager mFaceManager;
- private PackageManager mPackageManager;
+ private final ContentResolver mContentResolver;
+ private final UserManager mUserManager;
+ private final NotificationManager mNotificationManager;
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final StorageManager mStorageManager;
+ private final TrustManager mTrustManager;
+ private final KeyguardManager mKeyguardManager;
+ private final FingerprintManager mFingerprintManager;
+ private final FaceManager mFaceManager;
+ private final PackageManager mPackageManager;
- public MockLockSettingsContext(Context base, UserManager userManager,
- NotificationManager notificationManager, DevicePolicyManager devicePolicyManager,
- StorageManager storageManager, TrustManager trustManager,
- KeyguardManager keyguardManager, FingerprintManager fingerprintManager,
- FaceManager faceManager, PackageManager packageManager) {
+ public MockLockSettingsContext(Context base, ContentResolver contentResolver,
+ UserManager userManager, NotificationManager notificationManager,
+ DevicePolicyManager devicePolicyManager, StorageManager storageManager,
+ TrustManager trustManager, KeyguardManager keyguardManager,
+ FingerprintManager fingerprintManager, FaceManager faceManager,
+ PackageManager packageManager) {
super(base);
+ mContentResolver = contentResolver;
mUserManager = userManager;
mNotificationManager = notificationManager;
mDevicePolicyManager = devicePolicyManager;
@@ -63,6 +67,11 @@
}
@Override
+ public ContentResolver getContentResolver() {
+ return mContentResolver;
+ }
+
+ @Override
public Object getSystemService(String name) {
if (USER_SERVICE.equals(name)) {
return mUserManager;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 3f4bec6..b9cafa4 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -136,6 +136,43 @@
assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
}
+ protected void initializeSyntheticPassword(int userId) {
+ synchronized (mService.mSpManager) {
+ mService.initializeSyntheticPasswordLocked(userId);
+ }
+ }
+
+ // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
+ // that owns the FRP credential, if the device is already provisioned.
+ @Test
+ public void testFrpCredentialSyncedIfDeviceProvisioned() throws RemoteException {
+ setDeviceProvisioned(true);
+ initializeSyntheticPassword(PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
+ }
+
+ // Tests that the FRP credential is not updated when an LSKF-based protector is created for the
+ // user that owns the FRP credential, if the new credential is empty and the device is not yet
+ // provisioned.
+ @Test
+ public void testEmptyFrpCredentialNotSyncedIfDeviceNotProvisioned() throws RemoteException {
+ setDeviceProvisioned(false);
+ initializeSyntheticPassword(PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
+ }
+
+ // Tests that the FRP credential is updated when an LSKF-based protector is created for the user
+ // that owns the FRP credential, if the new credential is nonempty and the device is not yet
+ // provisioned.
+ @Test
+ public void testNonEmptyFrpCredentialSyncedIfDeviceNotProvisioned() throws RemoteException {
+ setDeviceProvisioned(false);
+ initializeSyntheticPassword(PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager, never()).setFrpCredentialHandle(any());
+ mService.setLockCredential(newPassword("password"), nonePassword(), PRIMARY_USER_ID);
+ verify(mStorage.mPersistentDataBlockManager).setFrpCredentialHandle(any());
+ }
+
@Test
public void testChangeCredential() throws RemoteException {
final LockscreenCredential password = newPassword("password");
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
index a3ac515..6c13a6f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeaverBasedSyntheticPasswordTests.java
@@ -1,11 +1,17 @@
package com.android.server.locksettings;
+import static org.junit.Assert.assertEquals;
+
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.locksettings.LockSettingsStorage.PersistentData;
+import com.google.android.collect.Sets;
+
import org.junit.Before;
+import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@@ -17,4 +23,36 @@
public void enableWeaver() throws Exception {
mSpManager.enableWeaver();
}
+
+ // Tests that if the device is not yet provisioned and the FRP credential uses Weaver, then the
+ // Weaver slot of the FRP credential is not reused. Assumes that Weaver slots are allocated
+ // sequentially, starting at slot 0.
+ @Test
+ public void testFrpWeaverSlotNotReused() {
+ final int userId = 10;
+ final int frpWeaverSlot = 0;
+
+ setDeviceProvisioned(false);
+ assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
+ new byte[1]);
+ initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+ assertEquals(Sets.newHashSet(1), mPasswordSlotManager.getUsedSlots());
+ }
+
+ // Tests that if the device is already provisioned and the FRP credential uses Weaver, then the
+ // Weaver slot of the FRP credential is reused. This is not a very interesting test by itself;
+ // it's here as a control for testFrpWeaverSlotNotReused().
+ @Test
+ public void testFrpWeaverSlotReused() {
+ final int userId = 10;
+ final int frpWeaverSlot = 0;
+
+ setDeviceProvisioned(true);
+ assertEquals(Sets.newHashSet(), mPasswordSlotManager.getUsedSlots());
+ mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, frpWeaverSlot, 0,
+ new byte[1]);
+ initializeSyntheticPassword(userId); // This should allocate a Weaver slot.
+ assertEquals(Sets.newHashSet(0), mPasswordSlotManager.getUsedSlots());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index ab52928..578b888 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -323,4 +323,19 @@
FakeIdmapDaemon.IdmapHeader idmap = idmapd.getIdmap(overlayPath);
assertEquals(0, CONFIG_SIGNATURE & idmap.policies);
}
+
+ @Test
+ public void testOnTargetSystemPackageUninstall() throws Exception {
+ installAndAssert(target(TARGET), USER,
+ Set.of(UserPackage.of(USER, TARGET)));
+ installAndAssert(overlay(OVERLAY, TARGET), USER,
+ Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
+ upgradeAndAssert(target(TARGET), USER,
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET)));
+
+ downgradeAndAssert(target(TARGET), USER,
+ Set.of(UserPackage.of(USER, TARGET)),
+ Set.of(UserPackage.of(USER, TARGET)));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index bba7669..dab4335 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
+import android.content.Intent;
import android.content.om.OverlayIdentifier;
import android.content.om.OverlayInfo;
import android.content.om.OverlayInfo.State;
@@ -160,7 +161,7 @@
* Adds the package to the device.
*
* This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
+ * {@link Intent#ACTION_PACKAGE_ADDED} broadcast.
*
* @throws IllegalStateException if the package is currently installed
*/
@@ -178,10 +179,10 @@
* Begins upgrading the package.
*
* This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
+ * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} extra and then receives the
+ * {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
@@ -194,7 +195,35 @@
throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
- assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName, userId));
+ assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
+ /* systemUpdateUninstall */ false, userId));
+ mState.add(pkg, userId);
+ assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
+ }
+
+ /**
+ * Begins downgrading the package. Usually used simulating a system uninstall of its /data
+ * variant.
+ *
+ * This corresponds to when the OMS receives the
+ * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} and {@link Intent#EXTRA_SYSTEM_UPDATE_UNINSTALL} extras
+ * and then receives the {@link Intent#ACTION_PACKAGE_ADDED} broadcast with the
+ * {@link Intent#EXTRA_REPLACING} extra.
+ *
+ * @throws IllegalStateException if the package is not currently installed
+ */
+ void downgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
+ @NonNull Set<UserPackage> onReplacingUpdatedPackages,
+ @NonNull Set<UserPackage> onReplacedUpdatedPackages)
+ throws OperationFailedException {
+ final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
+ if (replacedPackage == null) {
+ throw new IllegalStateException("package " + pkg.packageName + " not installed");
+ }
+
+ assertEquals(onReplacingUpdatedPackages, mImpl.onPackageReplacing(pkg.packageName,
+ /* systemUpdateUninstall */ true, userId));
mState.add(pkg, userId);
assertEquals(onReplacedUpdatedPackages, mImpl.onPackageReplaced(pkg.packageName, userId));
}
@@ -203,7 +232,7 @@
* Removes the package from the device.
*
* This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
+ * {@link Intent#ACTION_PACKAGE_REMOVED} broadcast.
*
* @throws IllegalStateException if the package is not currently installed
*/
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
new file mode 100644
index 0000000..c81fbb4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -0,0 +1,781 @@
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.PermissionChecker;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileAppsInternal;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+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.Collections;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class CrossProfileAppsServiceImplTest {
+ private static final String PACKAGE_ONE = "com.one";
+ private static final String FEATURE_ID = "feature.one";
+ private static final int PACKAGE_ONE_UID = 1111;
+ private static final ComponentName ACTIVITY_COMPONENT =
+ new ComponentName("com.one", "test");
+
+ private static final String PACKAGE_TWO = "com.two";
+ private static final int PACKAGE_TWO_UID = 2222;
+
+ private static final int PRIMARY_USER = 0;
+ private static final int PROFILE_OF_PRIMARY_USER = 10;
+ private static final int SECONDARY_USER = 11;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+ private TestInjector mTestInjector;
+ private ActivityInfo mActivityInfo;
+ private CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl;
+ private IApplicationThread mIApplicationThread;
+
+ private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+
+ @Before
+ public void initCrossProfileAppsServiceImpl() {
+ mTestInjector = new TestInjector();
+ LocalServices.removeServiceForTest(CrossProfileAppsInternal.class);
+ mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Before
+ public void setupEnabledProfiles() {
+ mUserEnabled.put(PRIMARY_USER, true);
+ mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
+ mUserEnabled.put(SECONDARY_USER, true);
+
+ when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ invocation -> {
+ List<Integer> users = new ArrayList<>();
+ final int targetUser = invocation.getArgument(0);
+ users.add(targetUser);
+
+ int profileUserId = -1;
+ if (targetUser == PRIMARY_USER) {
+ profileUserId = PROFILE_OF_PRIMARY_USER;
+ } else if (targetUser == PROFILE_OF_PRIMARY_USER) {
+ profileUserId = PRIMARY_USER;
+ }
+
+ if (profileUserId != -1 && mUserEnabled.get(profileUserId)) {
+ users.add(profileUserId);
+ }
+ return users.stream().mapToInt(i -> i).toArray();
+ });
+ }
+
+ @Before
+ public void setupCaller() {
+ mTestInjector.setCallingUid(PACKAGE_ONE_UID);
+ mTestInjector.setCallingUserId(PRIMARY_USER);
+ }
+
+ @Before
+ public void setupPackage() throws Exception {
+ // PACKAGE_ONE are installed in all users.
+ mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, true);
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, true);
+ mockAppsInstalled(PACKAGE_ONE, SECONDARY_USER, true);
+
+ // Packages are resolved to their corresponding UID.
+ doAnswer(invocation -> {
+ final int uid = invocation.getArgument(0);
+ final String packageName = invocation.getArgument(1);
+ if (uid == PACKAGE_ONE_UID && PACKAGE_ONE.equals(packageName)) {
+ return null;
+ } else if (uid ==PACKAGE_TWO_UID && PACKAGE_TWO.equals(packageName)) {
+ return null;
+ }
+ throw new SecurityException("Not matching");
+ }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+ // The intent is resolved to the ACTIVITY_COMPONENT.
+ mockActivityLaunchIntentResolvedTo(ACTIVITY_COMPONENT);
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_installed() throws Exception {
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).containsExactly(UserHandle.of(PROFILE_OF_PRIMARY_USER));
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_notInstalled() throws Exception {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_userNotEnabled() throws Exception {
+ mUserEnabled.put(PROFILE_OF_PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromSecondaryUser() throws Exception {
+ mTestInjector.setCallingUserId(SECONDARY_USER);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromProfile_installed() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).containsExactly(UserHandle.of(PRIMARY_USER));
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromProfile_notInstalled() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+ mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test(expected = SecurityException.class)
+ public void getTargetUserProfiles_fakeCaller() throws Exception {
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_TWO);
+ }
+
+ @Test
+ public void startActivityAsUser_currentUser() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_currentUser() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_notInstalled() throws Exception {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_notInstalled() {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_fakeCaller() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_TWO,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_fakeCaller() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_TWO,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_notExported() throws Exception {
+ mActivityInfo.exported = false;
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_notExported() {
+ try {
+ when(mPackageManager.getPermissionInfo(anyString(), anyInt()))
+ .thenReturn(new PermissionInfo());
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ mActivityInfo.exported = false;
+
+
+ // There's a bug in static mocking if the APK is large - so here is the next best thing...
+ doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+ .getSystemServiceName(PermissionCheckerManager.class);
+ PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+ .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+ AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+ anyBoolean(), anyInt());
+ doReturn(permissionCheckerManager).when(mContext).getSystemService(
+ Context.PERMISSION_CHECKER_SERVICE);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_profile_anotherPackage() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ new ComponentName(PACKAGE_TWO, "test"),
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_profile_anotherPackage() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ new ComponentName(PACKAGE_TWO, "test"),
+ UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_secondaryUser() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(SECONDARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startAnyActivityAsUser_secondaryUser() {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(SECONDARY_USER).getIdentifier(),
+ false,
+ /* targetTask */ null,
+ /* options */ null));
+
+ verify(mActivityTaskManagerInternal, never())
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ anyString(),
+ nullable(String.class),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ anyInt());
+ }
+
+ @Test
+ public void startActivityAsUser_fromProfile_success() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ /* targetTask */ null,
+ /* options */ null);
+
+ verify(mActivityTaskManagerInternal)
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ eq(PACKAGE_ONE),
+ eq(FEATURE_ID),
+ any(Intent.class),
+ nullable(IBinder.class),
+ anyInt(),
+ nullable(Bundle.class),
+ eq(PRIMARY_USER));
+ }
+
+ @Test
+ public void startActivityAsUser_sameTask_fromProfile_success() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+ Binder targetTask = new Binder();
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ mIApplicationThread,
+ PACKAGE_ONE,
+ FEATURE_ID,
+ ACTIVITY_COMPONENT,
+ UserHandle.of(PRIMARY_USER).getIdentifier(),
+ true,
+ targetTask,
+ options);
+ verify(mActivityTaskManagerInternal)
+ .startActivityAsUser(
+ nullable(IApplicationThread.class),
+ eq(PACKAGE_ONE),
+ eq(FEATURE_ID),
+ any(Intent.class),
+ eq(targetTask),
+ anyInt(),
+ eq(options),
+ eq(PRIMARY_USER));
+ }
+
+ private void mockAppsInstalled(String packageName, int user, boolean installed) {
+ when(mPackageManagerInternal.getPackageInfo(
+ eq(packageName),
+ anyLong(),
+ anyInt(),
+ eq(user)))
+ .thenReturn(installed ? createInstalledPackageInfo() : null);
+ }
+
+ private PackageInfo createInstalledPackageInfo() {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.enabled = true;
+ return packageInfo;
+ }
+
+ private void mockActivityLaunchIntentResolvedTo(ComponentName componentName) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = componentName.getPackageName();
+ activityInfo.name = componentName.getClassName();
+ activityInfo.exported = true;
+ resolveInfo.activityInfo = activityInfo;
+ mActivityInfo = activityInfo;
+
+ when(mPackageManagerInternal.queryIntentActivities(
+ any(Intent.class), nullable(String.class), anyLong(), anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(resolveInfo));
+ }
+
+ private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+ private int mCallingUid;
+ private int mCallingUserId;
+ private int mCallingPid;
+
+ public void setCallingUid(int uid) {
+ mCallingUid = uid;
+ }
+
+ public void setCallingPid(int pid) {
+ mCallingPid = pid;
+ }
+
+ public void setCallingUserId(int userId) {
+ mCallingUserId = userId;
+ }
+
+ @Override
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ @Override
+ public int getCallingPid() {
+ return mCallingPid;
+ }
+
+ @Override
+ public int getCallingUserId() {
+ return mCallingUserId;
+ }
+
+ @Override
+ public UserHandle getCallingUserHandle() {
+ return UserHandle.of(mCallingUserId);
+ }
+
+ @Override
+ public long clearCallingIdentity() {
+ return 0;
+ }
+
+ @Override
+ public void restoreCallingIdentity(long token) {
+ }
+
+ @Override
+ public void withCleanCallingIdentity(ThrowingRunnable action) {
+ action.run();
+ }
+
+ @Override
+ public <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
+ return action.get();
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public AppOpsManager getAppOpsManager() {
+ return mAppOpsManager;
+ }
+
+ @Override
+ public ActivityManagerInternal getActivityManagerInternal() {
+ return mActivityManagerInternal;
+ }
+
+ @Override
+ public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+ return mActivityTaskManagerInternal;
+ }
+
+ @Override
+ public IPackageManager getIPackageManager() {
+ return mIPackageManager;
+ }
+
+ @Override
+ public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+ return mDevicePolicyManagerInternal;
+ }
+
+ @Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+ mContext.sendBroadcastAsUser(intent, user);
+ }
+
+ @Override
+ public int checkComponentPermission(
+ String permission, int uid, int owningUid, boolean exported) {
+ return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+ }
+
+ @Override
+ public void killUid(int uid) {
+ PermissionManagerService.killUid(
+ UserHandle.getAppId(uid),
+ UserHandle.getUserId(uid),
+ PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED);
+ }
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 1815e2a..7c1962c 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
+import android.annotation.XmlRes;
import android.content.Context;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
@@ -50,6 +51,7 @@
.powerProfileModeledOnly()
.includePowerModels()
.build();
+ private final Context mContext;
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
@@ -67,14 +69,19 @@
}
public BatteryUsageStatsRule(long currentTime, File historyDir) {
- Context context = InstrumentationRegistry.getContext();
- mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+ mContext = InstrumentationRegistry.getContext();
+ mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
mMockClock.currentTime = currentTime;
mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.onSystemReady();
}
+ public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
+ mPowerProfile.forceInitForTesting(mContext, xmlId);
+ return this;
+ }
+
public BatteryUsageStatsRule setAveragePower(String key, double value) {
when(mPowerProfile.getAveragePower(key)).thenReturn(value);
when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 8262fca..5bc73bd 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.app.usage.NetworkStatsManager;
+import android.hardware.radio.V1_5.AccessNetwork;
import android.net.NetworkCapabilities;
import android.net.NetworkStats;
import android.os.BatteryConsumer;
@@ -34,6 +35,8 @@
import android.os.BatteryUsageStatsQuery;
import android.os.Process;
import android.os.UidBatteryConsumer;
+import android.telephony.ActivityStatsTechSpecificInfo;
+import android.telephony.CellSignalStrength;
import android.telephony.DataConnectionRealTimeInfo;
import android.telephony.ModemActivityInfo;
import android.telephony.ServiceState;
@@ -43,7 +46,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.os.PowerProfile;
+import com.android.frameworks.servicestests.R;
import com.google.common.collect.Range;
@@ -52,28 +55,25 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import java.util.ArrayList;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
@SuppressWarnings("GuardedBy")
public class MobileRadioPowerCalculatorTest {
private static final double PRECISION = 0.00001;
private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+ private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
@Mock
NetworkStatsManager mNetworkStatsManager;
@Rule
- public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
- .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE)
- .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON)
- .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0)
- .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
- .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
- .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
- new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
- .initMeasuredEnergyStatsLocked();
+ public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
@Test
public void testCounterBasedModel() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
// Scan for a cell
@@ -85,9 +85,15 @@
stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
5000, 5000);
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
// Note cell signal strength
SignalStrength signalStrength = mock(SignalStrength.class);
- when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -97,10 +103,31 @@
stats.noteNetworkInterfaceForTransports("cellular",
new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
// Note application network activity
NetworkStats networkStats = new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
mStatsRule.setNetworkStats(networkStats);
ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
@@ -108,34 +135,331 @@
stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
mNetworkStatsManager);
- mStatsRule.setTime(12_000, 12_000);
+ mStatsRule.setTime(10_000, 10_000);
MobileRadioPowerCalculator calculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
- UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
- assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(0.8);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // + 360 mA * 3000 ms (idle drain rate * idle duration)
+ // + 70 mA * 2000 ms (sleep drain rate * sleep duration)
+ // _________________
+ // = 4604000 mA-ms or 1.27888 mA-h
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(2.2444);
+ .isWithin(PRECISION).of(1.27888);
assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(0.8);
+ .isWithin(PRECISION).of(0.94);
assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.705);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.235);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ }
+
+ @Test
+ public void testCounterBasedModel_multipleDefinedRat() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+ .initMeasuredEnergyStatsLocked();
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+ mStatsRule.setNetworkStats(networkStats);
+
+ ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ new int[]{10, 11, 12, 13, 14}, 15);
+ ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+ new int[]{20, 21, 22, 23, 24}, 25);
+ ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+ new int[]{30, 31, 32, 33, 34}, 35);
+ ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+ new int[]{40, 41, 42, 43, 44}, 45);
+ ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+ new int[]{50, 51, 52, 53, 54}, 55);
+ ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+ AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+ new int[]{60, 61, 62, 63, 64}, 65);
+
+ ActivityStatsTechSpecificInfo[] ratInfos =
+ new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+ nrHighFreqInfo, nrMmwaveFreqInfo};
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+ stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+ mNetworkStatsManager);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+ // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+ // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+ // [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+ // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+ // [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+ // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+ // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+ // [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+ // _________________
+ // = 3957753 mA-ms or 1.09938 mA-h active consumption
+ //
+ // Idle drain rate * idle duration
+ // 360 mA * 3000 ms = 1080000 mA-ms
+ // Sleep drain rate * sleep duration
+ // 70 mA * 2000 ms = 140000 mA-ms
+ // _________________
+ // = 5177753 mA-ms or 1.43826 mA-h total consumption
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.43826);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.09938);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.82453);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.27484);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ }
+
+ @Test
+ public void testCounterBasedModel_legacyPowerProfile() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
+ BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+ // Scan for a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+ TelephonyManager.SIM_STATE_READY,
+ 2000, 2000);
+
+ // Found a cell
+ stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+ 5000, 5000);
+
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
+ // Note cell signal strength
+ SignalStrength signalStrength = mock(SignalStrength.class);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 8_000_000_000L, APP_UID, 8000, 8000);
+
+ // Note established network
+ stats.noteNetworkInterfaceForTransports("cellular",
+ new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+ stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+ 9_500_000_000L, APP_UID2, 9500, 9500);
+
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+ // Note application network activity
+ NetworkStats networkStats = new NetworkStats(10000, 1)
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+ .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+ mStatsRule.setNetworkStats(networkStats);
+
+ ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+ new int[]{100, 200, 300, 400, 500}, 600);
+ stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+ mNetworkStatsManager);
+
+ mStatsRule.setTime(10_000, 10_000);
+
+ MobileRadioPowerCalculator calculator =
+ new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+ mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+ BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // + 360 mA * 3000 ms (idle drain rate * idle duration)
+ // + 70 mA * 2000 ms (sleep drain rate * sleep duration)
+ // _________________
+ // = 4604000 mA-ms or 1.27888 mA-h
+ assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.27888);
+ assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
+ // + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
+ // + 1440 mA * 300 ms (level 2 TX drain rate * level 2 TX duration)
+ // + 1800 mA * 400 ms (level 3 TX drain rate * level 3 TX duration)
+ // + 2160 mA * 500 ms (level 4 TX drain rate * level 4 TX duration)
+ // + 1440 mA * 600 ms (RX drain rate * RX duration)
+ // _________________
+ // = 3384000 mA-ms or 0.94 mA-h
+ BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+ assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.94);
+ assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // 3/4 of total packets were sent by APP_UID so 75% of total
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.705);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+ // Rest should go to the other app
+ UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+ assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(0.235);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
public void testTimerBasedModel_byProcessState() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
@@ -148,13 +472,19 @@
TelephonyManager.SIM_STATE_READY,
2000, 2000);
+ ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+ CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ perRatCellStrength.add(gsmSignalStrength);
+
// Found a cell
stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
5000, 5000);
// Note cell signal strength
SignalStrength signalStrength = mock(SignalStrength.class);
- when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -164,10 +494,25 @@
stats.noteNetworkInterfaceForTransports("cellular",
new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+ // Spend some time in each signal strength level. It doesn't matter how long.
+ // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+ // timers.
+ when(gsmSignalStrength.getLevel()).thenReturn(
+ SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+ when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+ stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
// Note application network activity
mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
mNetworkStatsManager);
@@ -177,7 +522,7 @@
mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
mNetworkStatsManager);
@@ -199,6 +544,8 @@
MobileRadioPowerCalculator calculator =
new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+ mStatsRule.setTime(12_000, 12_000);
+
mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
.powerProfileModeledOnly()
.includePowerModels()
@@ -217,6 +564,10 @@
BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
+
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.6);
+
assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.2);
assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4);
assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
@@ -224,6 +575,8 @@
@Test
public void testMeasuredEnergyBasedModel() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
// Scan for a cell
@@ -264,12 +617,6 @@
mStatsRule.apply(calculator);
- UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
- assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isWithin(PRECISION).of(1.53934);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
// 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
@@ -282,10 +629,18 @@
.isWithin(PRECISION).of(1.53934);
assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+ UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+ assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isWithin(PRECISION).of(1.53934);
+ assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+ .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
@Test
public void testMeasuredEnergyBasedModel_byProcessState() {
+ mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+ .initMeasuredEnergyStatsLocked();
BatteryStatsImpl stats = mStatsRule.getBatteryStats();
BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
@@ -317,7 +672,7 @@
// Note application network activity
mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
@@ -326,7 +681,7 @@
mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
.addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index a03a1b4..ebcb498 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -234,6 +234,37 @@
}
@Test
+ public void testScheduleRepostsForLongTagPersistedNotification() throws Exception {
+ String longTag = "A".repeat(66000);
+ NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
+ mSnoozeHelper.snooze(r, 0);
+
+ // We store the full key in temp storage.
+ ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+ verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+ assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mSnoozeHelper.writeXml(serializer);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+ mSnoozeHelper.readXml(parser, 4);
+
+ mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+ // We trim the key in persistent storage.
+ verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+ assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+ }
+
+ @Test
public void testSnoozeForTime() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
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 85c4975..4e001fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -106,7 +106,7 @@
mTransition.mParticipants.add(mTopActivity);
mTopActivity.mTransitionController.moveToCollecting(mTransition);
// becomes invisible when covered by mTopActivity
- mTrampolineActivity.mVisibleRequested = false;
+ mTrampolineActivity.setVisibleRequested(false);
}
private <T> T verifyAsync(T mock) {
@@ -235,7 +235,7 @@
public void testOnActivityLaunchCancelled_hasDrawn() {
onActivityLaunched(mTopActivity);
- mTopActivity.mVisibleRequested = true;
+ mTopActivity.setVisibleRequested(true);
doReturn(true).when(mTopActivity).isReportedDrawn();
// Cannot time already-visible activities.
@@ -258,7 +258,7 @@
notifyActivityLaunching(noDrawnActivity.intent);
notifyAndVerifyActivityLaunched(noDrawnActivity);
- noDrawnActivity.mVisibleRequested = false;
+ noDrawnActivity.setVisibleRequested(false);
mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity));
@@ -286,7 +286,7 @@
clearInvocations(mLaunchObserver);
mLaunchTopByTrampoline = true;
- mTopActivity.mVisibleRequested = false;
+ mTopActivity.setVisibleRequested(false);
notifyActivityLaunching(mTopActivity.intent);
// It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
// the launch event is still valid.
@@ -314,7 +314,7 @@
// Create an invisible event that should be cancelled after the next event starts.
final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true).build();
onActivityLaunched(prev);
- prev.mVisibleRequested = false;
+ prev.setVisibleRequested(false);
mActivityOptions = ActivityOptions.makeBasic();
mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
@@ -561,7 +561,7 @@
@Test
public void testConsecutiveLaunchWithDifferentWindowingMode() {
mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
- mTrampolineActivity.mVisibleRequested = true;
+ mTrampolineActivity.setVisibleRequested(true);
onActivityLaunched(mTrampolineActivity);
mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
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 a410eed..f983fa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -917,7 +917,7 @@
// Prepare the activity record to be ready for immediate removal. It should be invisible and
// have no process. Otherwise, request to finish it will send a message to client first.
activity.setState(STOPPED, "test");
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
// Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
// this will cause NPE when updating task's process.
@@ -927,7 +927,7 @@
// next activity reports idle to destroy it.
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "test");
@@ -1082,7 +1082,7 @@
final ActivityRecord activity = createActivityWithTask();
clearInvocations(activity.mDisplayContent);
activity.finishing = false;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(RESUMED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1099,7 +1099,7 @@
final ActivityRecord activity = createActivityWithTask();
clearInvocations(activity.mDisplayContent);
activity.finishing = false;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(PAUSED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1118,7 +1118,7 @@
// Put an activity on top of test activity to make it invisible and prevent us from
// accidentally resuming the topmost one again.
new ActivityBuilder(mAtm).build();
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(STOPPED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1136,7 +1136,7 @@
final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
final ActivityRecord activity = createActivityWithTask();
activity.finishing = false;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(RESUMED, "test");
activity.finishIfPossible("test", false /* oomAdj */);
@@ -1273,7 +1273,7 @@
final ActivityRecord currentTop = createActivityWithTask();
final Task task = currentTop.getTask();
- currentTop.mVisibleRequested = currentTop.nowVisible = true;
+ currentTop.setVisibleRequested(currentTop.nowVisible = true);
// Simulates that {@code currentTop} starts an existing activity from background (so its
// state is stopped) and the starting flow just goes to place it at top.
@@ -1300,7 +1300,7 @@
final ActivityRecord bottomActivity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(bottomActivity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
// simulating bottomActivity as a trampoline activity.
bottomActivity.setState(RESUMED, "test");
bottomActivity.finishIfPossible("test", false);
@@ -1316,13 +1316,13 @@
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as not visible, so that we will wait for it before removing
// the top one.
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
activity.setState(STOPPED, "test");
@@ -1346,13 +1346,13 @@
final ActivityRecord topActivity = createActivityWithTask();
mDisplayContent.setIsSleeping(true);
doReturn(true).when(activity).shouldBeVisible();
- topActivity.mVisibleRequested = false;
+ topActivity.setVisibleRequested(false);
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the activity behind (on a separate task) as not visible
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
activity.setState(STOPPED, "test");
@@ -1370,13 +1370,13 @@
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = false;
+ topActivity.setVisibleRequested(false);
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the bottom activity as not visible, so that we would wait for it before removing
// the top one.
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.nowVisible = false;
activity.setState(STOPPED, "test");
@@ -1394,12 +1394,12 @@
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.nowVisible = true;
activity.setState(RESUMED, "test");
@@ -1417,12 +1417,12 @@
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = false;
+ topActivity.setVisibleRequested(false);
topActivity.nowVisible = false;
topActivity.finishing = true;
topActivity.setState(STOPPED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.nowVisible = true;
activity.setState(RESUMED, "test");
@@ -1440,12 +1440,12 @@
final ActivityRecord activity = createActivityWithTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm)
.setTask(activity.getTask()).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.finishing = true;
topActivity.setState(PAUSED, "true");
// Mark the bottom activity as already visible, so that there is no need to wait for it.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.nowVisible = true;
activity.setState(RESUMED, "test");
@@ -1454,7 +1454,7 @@
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord focusedActivity = stack.getTopMostActivity();
focusedActivity.nowVisible = true;
- focusedActivity.mVisibleRequested = true;
+ focusedActivity.setVisibleRequested(true);
focusedActivity.setState(RESUMED, "test");
stack.setResumedActivity(focusedActivity, "test");
@@ -1476,7 +1476,7 @@
int displayId = activity.getDisplayId();
doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
- topActivity.mVisibleRequested = true;
+ topActivity.setVisibleRequested(true);
topActivity.nowVisible = true;
topActivity.setState(RESUMED, "true");
doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -1515,12 +1515,12 @@
final ActivityRecord activity = createActivityWithTask();
final Task task = activity.getTask();
final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
- firstActivity.mVisibleRequested = false;
+ firstActivity.setVisibleRequested(false);
firstActivity.nowVisible = false;
firstActivity.setState(STOPPED, "test");
final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
- secondActivity.mVisibleRequested = true;
+ secondActivity.setVisibleRequested(true);
secondActivity.nowVisible = true;
secondActivity.setState(secondActivityState, "test");
@@ -1530,7 +1530,7 @@
} else {
translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
}
- translucentActivity.mVisibleRequested = true;
+ translucentActivity.setVisibleRequested(true);
translucentActivity.nowVisible = true;
translucentActivity.setState(RESUMED, "test");
@@ -1546,7 +1546,7 @@
// Finish the first activity
firstActivity.finishing = true;
- firstActivity.mVisibleRequested = true;
+ firstActivity.setVisibleRequested(true);
firstActivity.completeFinishing("test");
verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
0 /* configChanges */ , false /* preserveWindows */,
@@ -1614,7 +1614,7 @@
}, true /* traverseTopToBottom */);
activity.setState(STARTED, "test");
activity.finishing = true;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
// Try to finish the last activity above the home stack.
activity.completeFinishing("test");
@@ -1909,7 +1909,7 @@
// Simulate that the activity requests the same orientation as display.
activity.setOrientation(display.getConfiguration().orientation);
// Skip the real freezing.
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
clearInvocations(activity);
activity.onCancelFixedRotationTransform(originalRotation);
// The implementation of cancellation must be executed.
@@ -2535,7 +2535,7 @@
activity.setOccludesParent(true);
activity.setVisible(false);
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
// Can not specify orientation if app isn't visible even though it occludes parent.
assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
// Can specify orientation if the current orientation candidate is orientation behind.
@@ -2910,7 +2910,7 @@
task.addChild(taskFragment2, POSITION_TOP);
final ActivityRecord activity2 = new ActivityBuilder(mAtm)
.setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
taskFragment2.addChild(activity2);
assertTrue(activity2.isResizeable());
activity1.reparent(taskFragment1, POSITION_TOP);
@@ -3055,7 +3055,7 @@
.setCreateTask(true).build();
// By default, activity is visible.
assertTrue(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3064,7 +3064,7 @@
// until we verify no logic relies on this behavior, we'll keep this as is.
activity.setVisibility(true);
assertTrue(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
@@ -3075,7 +3075,7 @@
.setCreateTask(true).build();
// By default, activity is visible.
assertTrue(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3083,7 +3083,7 @@
// animation should be applied on this activity.
activity.setVisibility(false);
assertTrue(activity.isVisible());
- assertFalse(activity.mVisibleRequested);
+ assertFalse(activity.isVisibleRequested());
assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
assertTrue(activity.mDisplayContent.mClosingApps.contains(activity));
}
@@ -3095,7 +3095,7 @@
// Activiby is invisible. However ATMS requests it to become visible, since this is a top
// activity.
assertFalse(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3103,7 +3103,7 @@
// animation should be applied on this activity.
activity.setVisibility(true);
assertFalse(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3126,7 +3126,7 @@
// Activiby is invisible. However ATMS requests it to become visible, since this is a top
// activity.
assertFalse(activity.isVisible());
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
@@ -3134,7 +3134,7 @@
// transition should be applied on this activity.
activity.setVisibility(false);
assertFalse(activity.isVisible());
- assertFalse(activity.mVisibleRequested);
+ assertFalse(activity.isVisibleRequested());
assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
}
@@ -3549,12 +3549,12 @@
activity.reparent(taskFragment, POSITION_TOP);
// Ensure the activity visibility is updated even it is not shown to current user.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
doReturn(false).when(activity).showToCurrentUser();
spyOn(taskFragment);
doReturn(false).when(taskFragment).shouldBeVisible(any());
display.ensureActivitiesVisible(null, 0, false, false);
- assertFalse(activity.mVisibleRequested);
+ assertFalse(activity.isVisibleRequested());
}
@Test
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 0743ef4..ee31748 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -514,7 +514,9 @@
.setCreateActivity(true)
.build()
.getTopMostActivity();
- splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
+
+ splitPrimaryActivity.setVisibleRequested(true);
+ splitSecondActivity.setVisibleRequested(true);
assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
assertEquals(splitOrg.mSecondary, splitSecondActivity.getRootTask());
@@ -527,7 +529,7 @@
.setCreateActivity(true).build().getTopMostActivity();
final ActivityRecord translucentActivity = new TaskBuilder(mSupervisor)
.setCreateActivity(true).build().getTopMostActivity();
- assertTrue(activity.mVisibleRequested);
+ assertTrue(activity.isVisibleRequested());
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
false /* mockGetRootTask */);
@@ -1050,7 +1052,7 @@
ACTIVITY_TYPE_STANDARD, false /* onTop */));
// Activity should start invisible since we are bringing it to front.
singleTaskActivity.setVisible(false);
- singleTaskActivity.mVisibleRequested = false;
+ singleTaskActivity.setVisibleRequested(false);
// Create another activity on top of the secondary display.
final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN,
@@ -1268,7 +1270,7 @@
final ActivityStarter starter = prepareStarter(0 /* flags */);
final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
starter.mStartActivity = target;
- target.mVisibleRequested = false;
+ target.setVisibleRequested(false);
target.setTurnScreenOn(true);
// Assume the flag was consumed by relayout.
target.setCurrentLaunchCanTurnScreenOn(false);
@@ -1589,10 +1591,10 @@
final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
activityBot.setVisible(false);
- activityBot.mVisibleRequested = false;
+ activityBot.setVisibleRequested(false);
assertTrue(activityTop.isVisible());
- assertTrue(activityTop.mVisibleRequested);
+ assertTrue(activityTop.isVisibleRequested());
final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
| FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 2fccd64..368b750 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -344,7 +344,7 @@
// Assume the activity is finishing and hidden because it was crashed.
activity.finishing = true;
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setVisible(false);
activity.getTask().setPausingActivity(activity);
homeActivity.setState(PAUSED, "test");
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 fb94147c..c73237e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -122,7 +122,7 @@
final ActivityRecord top = createActivityRecord(task);
top.setState(ActivityRecord.State.RESUMED, "test");
behind.setState(ActivityRecord.State.STARTED, "test");
- behind.mVisibleRequested = true;
+ behind.setVisibleRequested(true);
task.removeActivities("test", false /* excludingTaskOverlay */);
assertFalse(mDisplayContent.mAppTransition.isReady());
@@ -294,7 +294,7 @@
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -319,12 +319,12 @@
// +- [Task2] - [ActivityRecord2] (opening, visible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(true);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.mRequestForceTransition = true;
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
activity2.mRequestForceTransition = true;
final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -391,7 +391,7 @@
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
attrs.setTitle("AppWindow2");
final TestWindowState appWindow2 = createWindowState(attrs, activity2);
appWindow2.mWillReplaceWindow = true;
@@ -424,17 +424,17 @@
// +- [ActivityRecord4] (invisible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
activity1.getTask());
activity2.setVisible(false);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
activity3.getTask());
activity4.setVisible(false);
- activity4.mVisibleRequested = false;
+ activity4.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -459,7 +459,7 @@
// +- [ActivityRecord2] (closing, visible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
activity1.getTask());
@@ -490,7 +490,7 @@
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setOccludesParent(false);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
@@ -528,13 +528,13 @@
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setOccludesParent(false);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
activity1.getTask());
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
activity3.setOccludesParent(false);
@@ -567,7 +567,7 @@
final Task parentTask = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -600,10 +600,10 @@
splitRoot1.setAdjacentTaskFragment(splitRoot2);
final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -625,12 +625,12 @@
final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
activity2.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -654,11 +654,11 @@
final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -683,11 +683,11 @@
final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
activity1.setVisible(true);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity2);
@@ -710,13 +710,13 @@
// +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final Task task2 = createTask(mDisplayContent);
task2.mRemoveWithTaskOrganizer = true;
final ActivityRecord activity2 = createActivityRecord(task2);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
opening.add(activity1);
@@ -744,7 +744,7 @@
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(false);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
final ActivityRecord activity2 = createActivityRecord(task);
final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -1260,6 +1260,8 @@
@Test
public void testTransitionGoodToGoForTaskFragments_detachedApp() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
final Task task = createTask(mDisplayContent);
final TaskFragment changeTaskFragment =
createTaskFragmentWithEmbeddedActivity(task, organizer);
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 437eeb1..6b814e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -385,11 +385,11 @@
// Simulate activity1 launches activity2.
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(true);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.allDrawn = true;
final ActivityRecord activity2 = createActivityRecord(task);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
activity2.allDrawn = true;
dc.mClosingApps.add(activity1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index dc3515d..3bce860 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -42,7 +42,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
-import android.window.BackEvent;
+import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
@@ -150,6 +150,7 @@
WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_WALLPAPER,
"Wallpaper");
addToWindowMap(window, true);
+ makeWindowVisibleAndDrawn(window);
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
window.setOnBackInvokedCallbackInfo(
@@ -236,6 +237,20 @@
1, appLatch.getCount());
}
+ @Test
+ public void backInfoWindowWithoutDrawn() {
+ WindowState window = createWindow(null, WindowManager.LayoutParams.TYPE_APPLICATION,
+ "TestWindow");
+ addToWindowMap(window, true);
+
+ IOnBackInvokedCallback callback = createOnBackInvokedCallback();
+ window.setOnBackInvokedCallbackInfo(
+ new OnBackInvokedCallbackInfo(callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT));
+
+ BackNavigationInfo backNavigationInfo = startBackNavigation();
+ assertThat(backNavigationInfo).isNull();
+ }
+
private IOnBackInvokedCallback withSystemCallback(Task task) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -259,11 +274,11 @@
private IOnBackInvokedCallback createOnBackInvokedCallback() {
return new IOnBackInvokedCallback.Stub() {
@Override
- public void onBackStarted(BackEvent backEvent) {
+ public void onBackStarted(BackMotionEvent backMotionEvent) {
}
@Override
- public void onBackProgressed(BackEvent backEvent) {
+ public void onBackProgressed(BackMotionEvent backMotionEvent) {
}
@Override
@@ -309,6 +324,7 @@
Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
mAtm.setFocusedTask(task.mTaskId, record);
addToWindowMap(window, true);
+ makeWindowVisibleAndDrawn(window);
return task;
}
@@ -333,6 +349,8 @@
addToWindowMap(window1, true);
addToWindowMap(window2, true);
+ makeWindowVisibleAndDrawn(window2);
+
CrossActivityTestCase testCase = new CrossActivityTestCase();
testCase.task = task;
testCase.recordBack = record1;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d151188..bc23fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -590,7 +590,7 @@
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
// Make sure top focused display not changed if there is a focused app.
- window1.mActivityRecord.mVisibleRequested = false;
+ window1.mActivityRecord.setVisibleRequested(false);
window1.getDisplayContent().setFocusedApp(window1.mActivityRecord);
updateFocusedWindow();
assertTrue(!window1.isFocused());
@@ -1106,7 +1106,7 @@
public void testOrientationBehind() {
final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
.setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
- prev.mVisibleRequested = false;
+ prev.setVisibleRequested(false);
final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
.setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
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 db1d15a..ba68a25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -243,7 +243,7 @@
}
@Override
- public boolean canShowTasksInRecents() {
+ public boolean canShowTasksInHostDeviceRecents() {
return true;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index ac2df62..6f2e3f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -819,7 +819,7 @@
@Test
public void testVisibleTask_displayCanNotShowTaskFromRecents_expectNotVisible() {
final DisplayContent displayContent = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
- doReturn(false).when(displayContent).canShowTasksInRecents();
+ doReturn(false).when(displayContent).canShowTasksInHostDeviceRecents();
final Task task = displayContent.getDefaultTaskDisplayArea().createRootTask(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
mRecentTasks.add(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58ea..08635ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -172,12 +172,12 @@
// executed.
final ActivityRecord activity1 = createActivityRecord(task);
activity1.setVisible(true);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
final ActivityRecord activity2 = createActivityRecord(task);
activity2.setVisible(false);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
mDefaultDisplay.getRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a2b4cb8..de3a526 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -111,7 +111,7 @@
RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
mRecentsComponent, true /* getRecentsAnimation */);
// The launch-behind state should make the recents activity visible.
- assertTrue(recentActivity.mVisibleRequested);
+ assertTrue(recentActivity.isVisibleRequested());
assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
mAtm.mDemoteTopAppReasons);
assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
@@ -119,7 +119,7 @@
// Simulate the animation is cancelled without changing the stack order.
recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
// The non-top recents activity should be invisible by the restored launch-behind state.
- assertFalse(recentActivity.mVisibleRequested);
+ assertFalse(recentActivity.isVisibleRequested());
assertEquals(0, mAtm.mDemoteTopAppReasons);
}
@@ -164,7 +164,7 @@
// The activity is started in background so it should be invisible and will be stopped.
assertThat(recentsActivity).isNotNull();
assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
- assertFalse(recentsActivity.mVisibleRequested);
+ assertFalse(recentsActivity.isVisibleRequested());
// Assume it is stopped to test next use case.
recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
@@ -360,7 +360,7 @@
true);
// Ensure we find the task for the right user and it is made visible
- assertTrue(otherUserHomeActivity.mVisibleRequested);
+ assertTrue(otherUserHomeActivity.isVisibleRequested());
}
private void startRecentsActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 881cc5f..fb29d3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1068,7 +1068,7 @@
activity.app = null;
overlayActivity.app = null;
// Simulate the process is dead
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(DESTROYED, "Test");
assertEquals(2, task.getChildCount());
@@ -1205,7 +1205,7 @@
// There is still an activity1 in rootTask1 so the activity2 should be added to finishing
// list that will be destroyed until idle.
- rootTask2.getTopNonFinishingActivity().mVisibleRequested = true;
+ rootTask2.getTopNonFinishingActivity().setVisibleRequested(true);
final ActivityRecord activity2 = finishTopActivity(rootTask2);
assertEquals(STOPPING, activity2.getState());
assertThat(mSupervisor.mStoppingActivities).contains(activity2);
@@ -1410,7 +1410,7 @@
new ActivityBuilder(mAtm).setTask(task).build();
// The scenario we are testing is when the app isn't visible yet.
nonTopVisibleActivity.setVisible(false);
- nonTopVisibleActivity.mVisibleRequested = false;
+ nonTopVisibleActivity.setVisibleRequested(false);
doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f84865b..a17e124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -174,7 +174,7 @@
final Task rootTask = new TaskBuilder(mSupervisor).build();
final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task1).build();
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
mWm.mRoot.rankTaskLayers();
assertEquals(1, task1.mLayerRank);
@@ -183,7 +183,7 @@
final Task task2 = new TaskBuilder(mSupervisor).build();
final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
mWm.mRoot.rankTaskLayers();
// Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
@@ -200,8 +200,8 @@
assertEquals(2, task2.mLayerRank);
// The rank should be updated to invisible when device went to sleep.
- activity1.mVisibleRequested = false;
- activity2.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
+ activity2.setVisibleRequested(false);
doReturn(true).when(mAtm).isSleepingOrShuttingDownLocked();
doReturn(true).when(mRootWindowContainer).putTasksToSleep(anyBoolean(), anyBoolean());
mSupervisor.mGoingToSleepWakeLock = mock(PowerManager.WakeLock.class);
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 e65610f..13ea99a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -169,7 +169,7 @@
public void testRestartProcessIfVisible() {
setUpDisplaySizeWithApp(1000, 2500);
doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
- mActivity.mVisibleRequested = true;
+ mActivity.setVisibleRequested(true);
mActivity.setSavedState(null /* savedState */);
mActivity.setState(RESUMED, "testRestart");
prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
@@ -553,7 +553,7 @@
resizeDisplay(display, 900, 1800);
mActivity.setState(STOPPED, "testSizeCompatMode");
- mActivity.mVisibleRequested = false;
+ mActivity.setVisibleRequested(false);
mActivity.visibleIgnoringKeyguard = false;
mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
mActivity.app.computeProcessActivityState();
@@ -605,7 +605,7 @@
// Make the activity resizable again by restarting it
clearInvocations(mTask);
mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
- mActivity.mVisibleRequested = true;
+ mActivity.setVisibleRequested(true);
mActivity.restartProcessIfVisible();
// The full lifecycle isn't hooked up so manually set state to resumed
mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
@@ -3188,7 +3188,7 @@
task.mResizeMode = activity.info.resizeMode;
task.getRootActivity().info.resizeMode = activity.info.resizeMode;
}
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
if (maxAspect >= 0) {
activity.info.setMaxAspectRatio(maxAspect);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index d5fb1a8..91f8d8d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -398,7 +398,7 @@
.setParentTask(rootHomeTask).setCreateTask(true).build();
}
homeActivity.setVisible(false);
- homeActivity.mVisibleRequested = true;
+ homeActivity.setVisibleRequested(true);
assertFalse(rootHomeTask.isVisible());
assertEquals(defaultTaskDisplayArea.getOrientation(), rootHomeTask.getOrientation());
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 2b49314..834302e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -370,7 +370,8 @@
mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- assertTaskFragmentParentInfoChangedTransaction(task);
+ // There will not be TaskFragmentParentInfoChanged because Task visible request is changed
+ // before the organized TaskFragment is added to the Task.
assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
}
@@ -1159,6 +1160,7 @@
doReturn(false).when(task).shouldBeVisible(any());
// Dispatch the initial event in the Task to update the Task visibility to the organizer.
+ clearInvocations(mOrganizer);
mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
mController.dispatchPendingEvents();
verify(mOrganizer).onTransactionReady(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 59a31b1..aaf07b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -135,8 +135,8 @@
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = false;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -199,9 +199,9 @@
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = false;
- opening.mVisibleRequested = true;
- opening2.mVisibleRequested = true;
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
+ opening2.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -250,8 +250,8 @@
fillChangeMap(changes, tda);
// End states.
- showing.mVisibleRequested = true;
- showing2.mVisibleRequested = true;
+ showing.setVisibleRequested(true);
+ showing2.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -286,16 +286,16 @@
final Task openTask = createTask(mDisplayContent);
final ActivityRecord opening = createActivityRecord(openTask);
- opening.mVisibleRequested = false; // starts invisible
+ opening.setVisibleRequested(false); // starts invisible
final Task closeTask = createTask(mDisplayContent);
final ActivityRecord closing = createActivityRecord(closeTask);
- closing.mVisibleRequested = true; // starts visible
+ closing.setVisibleRequested(true); // starts visible
transition.collectExistenceChange(openTask);
transition.collect(opening);
transition.collect(closing);
- opening.mVisibleRequested = true;
- closing.mVisibleRequested = false;
+ opening.setVisibleRequested(true);
+ closing.setVisibleRequested(false);
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
@@ -323,7 +323,7 @@
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
- act.mVisibleRequested = (i % 2) == 0; // starts invisible
+ act.setVisibleRequested((i % 2) == 0); // starts invisible
}
// doesn't matter which order collected since participants is a set
@@ -331,7 +331,7 @@
transition.collectExistenceChange(tasks[i]);
final ActivityRecord act = tasks[i].getTopMostActivity();
transition.collect(act);
- tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+ tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -360,7 +360,7 @@
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
final ActivityRecord act = createActivityRecord(tasks[i]);
// alternate so that the transition doesn't get promoted to the display area
- act.mVisibleRequested = (i % 2) == 0; // starts invisible
+ act.setVisibleRequested((i % 2) == 0); // starts invisible
act.visibleIgnoringKeyguard = (i % 2) == 0;
if (i == showWallpaperTask) {
doReturn(true).when(act).showWallpaper();
@@ -381,7 +381,7 @@
transition.collectExistenceChange(tasks[i]);
final ActivityRecord act = tasks[i].getTopMostActivity();
transition.collect(act);
- tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+ tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
}
ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -417,9 +417,9 @@
changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
fillChangeMap(changes, topTask);
// End states.
- showing.mVisibleRequested = true;
- closing.mVisibleRequested = false;
- hiding.mVisibleRequested = false;
+ showing.setVisibleRequested(true);
+ closing.setVisibleRequested(false);
+ hiding.setVisibleRequested(false);
participants.add(belowTask);
participants.add(hiding);
@@ -449,9 +449,9 @@
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, topTask);
// End states.
- showing.mVisibleRequested = true;
- opening.mVisibleRequested = true;
- closing.mVisibleRequested = false;
+ showing.setVisibleRequested(true);
+ opening.setVisibleRequested(true);
+ closing.setVisibleRequested(false);
participants.add(belowTask);
participants.add(showing);
@@ -531,19 +531,19 @@
@Test
public void testOpenActivityInTheSameTaskWithDisplayChange() {
final ActivityRecord closing = createActivityRecord(mDisplayContent);
- closing.mVisibleRequested = true;
+ closing.setVisibleRequested(true);
final Task task = closing.getTask();
makeTaskOrganized(task);
final ActivityRecord opening = createActivityRecord(task);
- opening.mVisibleRequested = false;
+ opening.setVisibleRequested(false);
makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
final Transition transition = createTestTransition(TRANSIT_OPEN);
for (WindowContainer<?> wc : wcs) {
transition.collect(wc);
}
- closing.mVisibleRequested = false;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(false);
+ opening.setVisibleRequested(true);
final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
for (WindowContainer<?> wc : wcs) {
wc.getWindowConfiguration().setRotation(newRotation);
@@ -586,9 +586,9 @@
changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, openTask);
// End states.
- changeInChange.mVisibleRequested = true;
- openInOpen.mVisibleRequested = true;
- openInChange.mVisibleRequested = true;
+ changeInChange.setVisibleRequested(true);
+ openInOpen.setVisibleRequested(true);
+ openInChange.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -644,8 +644,8 @@
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = true;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(true);
+ opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -685,8 +685,8 @@
changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
fillChangeMap(changes, newTask);
// End states.
- closing.mVisibleRequested = true;
- opening.mVisibleRequested = true;
+ closing.setVisibleRequested(true);
+ opening.setVisibleRequested(true);
final int transit = transition.mType;
int flags = 0;
@@ -962,7 +962,7 @@
home.mTransitionController.requestStartTransition(transition, home.getTask(),
null /* remoteTransition */, null /* displayChange */);
transition.collectExistenceChange(home);
- home.mVisibleRequested = true;
+ home.setVisibleRequested(true);
mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
doReturn(true).when(home).hasFixedRotationTransform(any());
player.startTransition();
@@ -998,12 +998,12 @@
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(task1);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task1);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
@@ -1011,9 +1011,9 @@
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
// Using abort to force-finish the sync (since we can't wait for drawing in unit test).
// We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1029,8 +1029,8 @@
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
- activity1.mVisibleRequested = false;
- activity2.mVisibleRequested = true;
+ activity1.setVisibleRequested(false);
+ activity2.setVisibleRequested(true);
openTransition.finishTransition();
@@ -1072,12 +1072,12 @@
// Start out with task2 visible and set up a transition that closes task2 and opens task1
final Task task1 = createTask(mDisplayContent);
final ActivityRecord activity1 = createActivityRecord(task1);
- activity1.mVisibleRequested = false;
+ activity1.setVisibleRequested(false);
activity1.setVisible(false);
final Task task2 = createTask(mDisplayContent);
makeTaskOrganized(task1, task2);
final ActivityRecord activity2 = createActivityRecord(task2);
- activity2.mVisibleRequested = true;
+ activity2.setVisibleRequested(true);
activity2.setVisible(true);
openTransition.collectExistenceChange(task1);
@@ -1085,9 +1085,9 @@
openTransition.collectExistenceChange(task2);
openTransition.collectExistenceChange(activity2);
- activity1.mVisibleRequested = true;
+ activity1.setVisibleRequested(true);
activity1.setVisible(true);
- activity2.mVisibleRequested = false;
+ activity2.setVisibleRequested(false);
// Using abort to force-finish the sync (since we can't wait for drawing in unit test).
// We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1107,8 +1107,8 @@
closeTransition.collectExistenceChange(activity2);
closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
- activity1.mVisibleRequested = false;
- activity2.mVisibleRequested = true;
+ activity1.setVisibleRequested(false);
+ activity2.setVisibleRequested(true);
activity2.setVisible(true);
// Using abort to force-finish the sync (since we obviously can't wait for drawing).
@@ -1166,8 +1166,8 @@
changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
// End states.
- activity0.mVisibleRequested = false;
- activity1.mVisibleRequested = true;
+ activity0.setVisibleRequested(false);
+ activity1.setVisibleRequested(true);
participants.add(activity0);
participants.add(activity1);
@@ -1210,9 +1210,9 @@
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
false /* exChg */));
// End states.
- closingActivity.mVisibleRequested = false;
- openingActivity.mVisibleRequested = true;
- nonEmbeddedActivity.mVisibleRequested = false;
+ closingActivity.setVisibleRequested(false);
+ openingActivity.setVisibleRequested(true);
+ nonEmbeddedActivity.setVisibleRequested(false);
participants.add(closingActivity);
participants.add(openingActivity);
@@ -1255,8 +1255,8 @@
false /* exChg */));
changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
// End states.
- nonEmbeddedActivity.mVisibleRequested = false;
- embeddedActivity.mVisibleRequested = true;
+ nonEmbeddedActivity.setVisibleRequested(false);
+ embeddedActivity.setVisibleRequested(true);
embeddedTf.setBounds(new Rect(0, 0, 500, 500));
participants.add(nonEmbeddedActivity);
@@ -1285,11 +1285,11 @@
final ActivityRecord activity = createActivityRecord(task);
// Start states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: reset bounds to fill Task.
activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1313,11 +1313,11 @@
task.getConfiguration().windowConfiguration.setBounds(taskBounds);
final ActivityRecord activity = createActivityRecord(task);
// Start states: fills Task without override.
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
changes.put(activity, new Transition.ChangeInfo(activity));
// End states: set bounds to make sure the start bounds is ignored if it is not visible.
activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1340,12 +1340,12 @@
final Task lastParent = createTask(mDisplayContent);
final Task newParent = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(lastParent);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
changes.put(activity, new Transition.ChangeInfo(activity));
activity.reparent(newParent, POSITION_TOP);
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
participants.add(activity);
final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1365,7 +1365,7 @@
final Task task = createTask(mDisplayContent);
task.setBounds(new Rect(0, 0, 2000, 1000));
final ActivityRecord activity = createActivityRecord(task);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
// Skip manipulate the SurfaceControl.
doNothing().when(activity).setDropInputMode(anyInt());
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
@@ -1413,13 +1413,13 @@
task.setTaskDescription(taskDescription);
// Start states:
- embeddedActivity.mVisibleRequested = true;
- nonEmbeddedActivity.mVisibleRequested = false;
+ embeddedActivity.setVisibleRequested(true);
+ nonEmbeddedActivity.setVisibleRequested(false);
changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
// End states:
- embeddedActivity.mVisibleRequested = false;
- nonEmbeddedActivity.mVisibleRequested = true;
+ embeddedActivity.setVisibleRequested(false);
+ nonEmbeddedActivity.setVisibleRequested(true);
participants.add(embeddedTf);
participants.add(nonEmbeddedActivity);
@@ -1532,7 +1532,7 @@
final ActivityRecord activity = createActivityRecord(lastParent);
doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
doNothing().when(activity).setDropInputMode(anyInt());
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
activity.mTransitionController, mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 45e1141..2fccb88a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -95,7 +95,7 @@
final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
activity.finishing = true;
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setVisibility(false, false);
assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 94b5b93..a100b9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -313,12 +313,12 @@
r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
// Invisible requested activity should not share its rotation transform.
- r.mVisibleRequested = false;
+ r.setVisibleRequested(false);
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
assertFalse(wallpaperToken.hasFixedRotationTransform());
// Wallpaper should link the transform of its target.
- r.mVisibleRequested = true;
+ r.setVisibleRequested(true);
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
assertTrue(r.hasFixedRotationTransform());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 871030f..3d777f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -205,7 +205,7 @@
win.mViewVisibility = View.VISIBLE;
win.mHasSurface = true;
win.mActivityRecord.mAppStopped = true;
- win.mActivityRecord.mVisibleRequested = false;
+ win.mActivityRecord.setVisibleRequested(false);
win.mActivityRecord.setVisible(false);
mWm.mWindowMap.put(win.mClient.asBinder(), win);
final int w = 100;
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 c73e237..e5e9f54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -992,7 +992,7 @@
final Task task = createTask(rootTaskController1);
final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
- w.mActivityRecord.mVisibleRequested = true;
+ w.mActivityRecord.setVisibleRequested(true);
w.mActivityRecord.setVisible(true);
BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 3abf7ce..8bd4148 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -324,7 +324,7 @@
@Test
public void testComputeOomAdjFromActivities() {
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
final int[] callbackResult = { 0 };
final int visible = 1;
final int paused = 2;
@@ -359,7 +359,7 @@
assertEquals(visible, callbackResult[0]);
callbackResult[0] = 0;
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(PAUSED, "test");
mWpc.computeOomAdjFromActivities(callback);
assertEquals(paused, callbackResult[0]);
@@ -380,7 +380,7 @@
final VisibleActivityProcessTracker tracker = mAtm.mVisibleActivityProcessTracker;
spyOn(tracker);
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setState(STARTED, "test");
verify(tracker).onAnyActivityVisible(mWpc);
@@ -398,7 +398,7 @@
assertTrue(mWpc.hasForegroundActivities());
activity.setVisibility(false);
- activity.mVisibleRequested = false;
+ activity.setVisibleRequested(false);
activity.setState(STOPPED, "test");
verify(tracker).onAllActivitiesInvisible(mWpc);
@@ -413,7 +413,7 @@
@Test
public void testTopActivityUiModeChangeScheduleConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
@@ -423,7 +423,7 @@
@Test
public void testTopActivityUiModeChangeForDifferentPackage_noScheduledConfigChange() {
final ActivityRecord activity = createActivityRecord(mWpc);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
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 1b79dd3..435c39c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -264,7 +264,7 @@
// Verify that app window can still be IME target as long as it is visible (even if
// it is going to become invisible).
- appWindow.mActivityRecord.mVisibleRequested = false;
+ appWindow.mActivityRecord.setVisibleRequested(false);
assertTrue(appWindow.canBeImeTarget());
// Make windows invisible
@@ -720,7 +720,7 @@
// No need to wait for a window of invisible activity even if the window has surface.
final WindowState invisibleApp = mAppWindow;
- invisibleApp.mActivityRecord.mVisibleRequested = false;
+ invisibleApp.mActivityRecord.setVisibleRequested(false);
invisibleApp.mActivityRecord.allDrawn = false;
outWaitingForDrawn.clear();
invisibleApp.requestDrawIfNeeded(outWaitingForDrawn);
@@ -738,7 +738,7 @@
assertFalse(startingApp.getOrientationChanging());
// Even if the display is frozen, invisible requested window should not be affected.
- startingApp.mActivityRecord.mVisibleRequested = false;
+ startingApp.mActivityRecord.setVisibleRequested(false);
mWm.startFreezingDisplay(0, 0, mDisplayContent);
doReturn(true).when(mWm.mPolicy).isScreenOn();
startingApp.getWindowFrames().setInsetsChanged(true);
@@ -813,7 +813,7 @@
final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
"App window");
doReturn(true).when(embeddedActivity).isVisible();
- embeddedActivity.mVisibleRequested = true;
+ embeddedActivity.setVisibleRequested(true);
makeWindowVisible(win);
win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
// Set the bounds twice:
@@ -838,7 +838,7 @@
@Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
- win0.mActivityRecord.mVisibleRequested = false;
+ win0.mActivityRecord.setVisibleRequested(false);
assertFalse(win0.canReceiveTouchInput());
}
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 5a261bc65..019b14d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -738,7 +738,7 @@
activity.onDisplayChanged(dc);
activity.setOccludesParent(true);
activity.setVisible(true);
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
}
/**
@@ -1240,7 +1240,7 @@
mTask.moveToFront("createActivity");
}
if (mVisible) {
- activity.mVisibleRequested = true;
+ activity.setVisibleRequested(true);
activity.setVisible(true);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
new file mode 100644
index 0000000..e82a7c2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.wm.utils;
+
+import static com.android.server.wm.utils.StateMachine.isIn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ * atest WmTests:StateMachineTest
+ */
+@SmallTest
+@Presubmit
+public class StateMachineTest {
+ static class LoggingHandler implements StateMachine.Handler {
+ final int mState;
+ final StringBuffer mStringBuffer;
+ // True if process #handle
+ final boolean mHandleSelf;
+
+ LoggingHandler(int state, StringBuffer sb, boolean handleSelf) {
+ mHandleSelf = handleSelf;
+ mState = state;
+ mStringBuffer = sb;
+ }
+
+ LoggingHandler(int state, StringBuffer sb) {
+ this(state, sb, true /* handleSelf */);
+ }
+
+ @Override
+ public void enter() {
+ mStringBuffer.append('i');
+ mStringBuffer.append(Integer.toHexString(mState));
+ mStringBuffer.append(';');
+ }
+
+ @Override
+ public void exit() {
+ mStringBuffer.append('o');
+ mStringBuffer.append(Integer.toHexString(mState));
+ mStringBuffer.append(';');
+ }
+
+ @Override
+ public boolean handle(int event, Object param) {
+ if (mHandleSelf) {
+ mStringBuffer.append('h');
+ mStringBuffer.append(Integer.toHexString(mState));
+ mStringBuffer.append(';');
+ }
+ return mHandleSelf;
+ }
+ }
+
+ static class LoggingHandlerTransferInExit extends LoggingHandler {
+ final StateMachine mStateMachine;
+ final int mStateToTransit;
+
+ LoggingHandlerTransferInExit(int state, StringBuffer sb, StateMachine stateMachine,
+ int stateToTransit) {
+ super(state, sb);
+ mStateMachine = stateMachine;
+ mStateToTransit = stateToTransit;
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mStateMachine.transit(mStateToTransit);
+ }
+ }
+
+ @Test
+ public void testStateMachineIsIn() {
+ assertTrue(isIn(0x112, 0x1));
+ assertTrue(isIn(0x112, 0x11));
+ assertTrue(isIn(0x112, 0x112));
+
+ assertFalse(isIn(0x1, 0x112));
+ assertFalse(isIn(0x12, 0x2));
+ }
+
+ @Test
+ public void testStateMachineInitialState() {
+ StateMachine stateMachine = new StateMachine();
+ assertEquals(0, stateMachine.getState());
+
+ stateMachine = new StateMachine(0x23);
+ assertEquals(0x23, stateMachine.getState());
+ }
+
+ @Test
+ public void testStateMachineTransitToChild() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine();
+ stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+ stateMachine.addStateHandler(0x12, new LoggingHandler(0x12, log));
+ stateMachine.addStateHandler(0x123, new LoggingHandler(0x123, log));
+ stateMachine.addStateHandler(0x1233, new LoggingHandler(0x1233, log));
+
+ // 0x0 -> 0x12
+ stateMachine.transit(0x12);
+ assertEquals("i1;i12;", log.toString());
+ assertEquals(0x12, stateMachine.getState());
+
+ // 0x12 -> 0x1233
+ log.setLength(0);
+ stateMachine.transit(0x1233);
+ assertEquals(0x1233, stateMachine.getState());
+ assertEquals("i123;i1233;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTransitToParent() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+ // 0x253 -> 0x2
+ stateMachine.transit(0x2);
+ assertEquals(0x2, stateMachine.getState());
+ assertEquals("o253;o25;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTransitSelf() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+ // 0x253 -> 0x253
+ stateMachine.transit(0x253);
+ assertEquals(0x253, stateMachine.getState());
+ assertEquals("o253;i253;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTransitGeneral() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x1351);
+ stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+ stateMachine.addStateHandler(0x13, new LoggingHandler(0x13, log));
+ stateMachine.addStateHandler(0x132, new LoggingHandler(0x132, log));
+ stateMachine.addStateHandler(0x1322, new LoggingHandler(0x1322, log));
+ stateMachine.addStateHandler(0x1322, new LoggingHandler(0x1322, log));
+ stateMachine.addStateHandler(0x135, new LoggingHandler(0x135, log));
+ stateMachine.addStateHandler(0x1351, new LoggingHandler(0x1351, log));
+
+ // 0x1351 -> 0x1322
+ // least common ancestor = 0x13
+ stateMachine.transit(0x1322);
+ assertEquals(0x1322, stateMachine.getState());
+ assertEquals("o1351;o135;i132;i1322;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTriggerStateAction() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253, new LoggingHandler(0x253, log));
+
+ // state 0x253 handles the message itself
+ stateMachine.handle(0, null);
+ assertEquals("h253;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineTriggerStateActionDelegate() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x253);
+ stateMachine.addStateHandler(0x2, new LoggingHandler(0x2, log));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x253,
+ new LoggingHandler(0x253, log, false /* handleSelf */));
+
+ // state 0x253 delegate the message handling to its parent state
+ stateMachine.handle(0, null);
+ assertEquals("h25;", log.toString());
+ }
+
+ @Test
+ public void testStateMachineNestedTransition() {
+ final StringBuffer log = new StringBuffer();
+
+ StateMachine stateMachine = new StateMachine(0x25);
+ stateMachine.addStateHandler(0x1, new LoggingHandler(0x1, log));
+
+ // Force transit to state 0x3 in exit()
+ stateMachine.addStateHandler(0x2,
+ new LoggingHandlerTransferInExit(0x2, log, stateMachine, 0x3));
+ stateMachine.addStateHandler(0x25, new LoggingHandler(0x25, log));
+ stateMachine.addStateHandler(0x3, new LoggingHandler(0x3, log));
+
+ stateMachine.transit(0x1);
+ // Start transit to 0x1
+ // 0x25 -> 0x2 [transit(0x3) requested] -> 0x1
+ // 0x1 -> 0x3
+ // Immediately set the status to 0x1, no enter/exit
+ assertEquals("o25;o2;i1;o1;i3;", log.toString());
+ }
+}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4fd2b78..b3a1f2b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1864,6 +1864,21 @@
mResponseStatsTracker.dump(idpw);
}
return;
+ } else if ("app-component-usage".equals(arg)) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ synchronized (mLock) {
+ if (!mLastTimeComponentUsedGlobal.isEmpty()) {
+ ipw.println("App Component Usages:");
+ ipw.increaseIndent();
+ for (String pkg : mLastTimeComponentUsedGlobal.keySet()) {
+ ipw.println("package=" + pkg
+ + " lastUsed=" + UserUsageStatsService.formatDateTime(
+ mLastTimeComponentUsedGlobal.get(pkg), true));
+ }
+ ipw.decreaseIndent();
+ }
+ }
+ return;
} else if (arg != null && !arg.startsWith("-")) {
// Anything else that doesn't start with '-' is a pkg to filter
pkgs.add(arg);
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index ffdb07b..b6aed2db 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -219,7 +219,7 @@
private static EventLogger sEventLogger;
- private UsbGadgetHal mUsbGadgetHal;
+ private static UsbGadgetHal mUsbGadgetHal;
/**
* Counter for tracking UsbOperation operations.
@@ -1976,7 +1976,7 @@
}
}
- private final class UsbHandlerHal extends UsbHandler {
+ private static final class UsbHandlerHal extends UsbHandler {
private final Object mGadgetProxyLock = new Object();
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 8ec500b..7eccd1a 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -49,7 +49,6 @@
POLICY_RULE_DO_NOT_DELETE,
POLICY_RULE_DELETE_AFTER_DISABLING
})
- /** @hide */
public @interface PolicyRule {}
/** Once this profile is enabled, it cannot be disabled. */
public static final int POLICY_RULE_DO_NOT_DISABLE = 1;
@@ -66,7 +65,6 @@
PROFILE_CLASS_OPERATIONAL,
PROFILE_CLASS_UNSET
})
- /** @hide */
public @interface ProfileClass {}
/** Testing profiles */
public static final int PROFILE_CLASS_TESTING = 0;
@@ -87,7 +85,6 @@
PROFILE_STATE_ENABLED,
PROFILE_STATE_UNSET
})
- /** @hide */
public @interface ProfileState {}
/** Disabled profiles */
public static final int PROFILE_STATE_DISABLED = 0;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 6be2f77..7c600b1 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,6 +138,12 @@
*/
public static final int FREQUENCY_RANGE_MMWAVE = 4;
+ /**
+ * Number of frequency ranges.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_COUNT = 5;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "DUPLEX_MODE_",
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index db9dfbb..3b84b65 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -649,6 +649,15 @@
}
/**
+ * @return {@code true} if the subscription is from the actively used SIM.
+ *
+ * @hide
+ */
+ public boolean isActive() {
+ return mSimSlotIndex >= 0 || mType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
+ }
+
+ /**
* Used in scenarios where different subscriptions are bundled as a group.
* It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
* Such that those subscriptions will have some affiliated behaviors such as opportunistic
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 5c1d497..5244f41 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -155,6 +155,10 @@
private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
"cache_key.telephony.get_slot_index";
+ /** The IPC cache key shared by all subscription manager service cacheable properties. */
+ private static final String CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY =
+ "cache_key.telephony.subscription_manager_service";
+
/** @hide */
public static final String GET_SIM_SPECIFIC_SETTINGS_METHOD_NAME = "getSimSpecificSettings";
@@ -269,37 +273,72 @@
CACHE_KEY_DEFAULT_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetDefaultSubIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getDefaultSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static VoidPropertyInvalidatedCache<Integer> sDefaultDataSubIdCache =
new VoidPropertyInvalidatedCache<>(ISub::getDefaultDataSubId,
CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetDefaultDataSubIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getDefaultDataSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static VoidPropertyInvalidatedCache<Integer> sDefaultSmsSubIdCache =
new VoidPropertyInvalidatedCache<>(ISub::getDefaultSmsSubId,
CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetDefaultSmsSubIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getDefaultSmsSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static VoidPropertyInvalidatedCache<Integer> sActiveDataSubIdCache =
new VoidPropertyInvalidatedCache<>(ISub::getActiveDataSubscriptionId,
CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static VoidPropertyInvalidatedCache<Integer> sGetActiveDataSubscriptionIdCache =
+ new VoidPropertyInvalidatedCache<>(ISub::getActiveDataSubscriptionId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
private static IntegerPropertyInvalidatedCache<Integer> sSlotIndexCache =
new IntegerPropertyInvalidatedCache<>(ISub::getSlotIndex,
CACHE_KEY_SLOT_INDEX_PROPERTY,
INVALID_SIM_SLOT_INDEX);
+ private static IntegerPropertyInvalidatedCache<Integer> sGetSlotIndexCache =
+ new IntegerPropertyInvalidatedCache<>(ISub::getSlotIndex,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SIM_SLOT_INDEX);
+
private static IntegerPropertyInvalidatedCache<Integer> sSubIdCache =
new IntegerPropertyInvalidatedCache<>(ISub::getSubId,
CACHE_KEY_SLOT_INDEX_PROPERTY,
INVALID_SUBSCRIPTION_ID);
+ private static IntegerPropertyInvalidatedCache<Integer> sGetSubIdCache =
+ new IntegerPropertyInvalidatedCache<>(ISub::getSubId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_SUBSCRIPTION_ID);
+
/** Cache depends on getDefaultSubId, so we use the defaultSubId cache key */
private static IntegerPropertyInvalidatedCache<Integer> sPhoneIdCache =
new IntegerPropertyInvalidatedCache<>(ISub::getPhoneId,
CACHE_KEY_DEFAULT_SUB_ID_PROPERTY,
INVALID_PHONE_INDEX);
+ private static IntegerPropertyInvalidatedCache<Integer> sGetPhoneIdCache =
+ new IntegerPropertyInvalidatedCache<>(ISub::getPhoneId,
+ CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY,
+ INVALID_PHONE_INDEX);
+
/**
* Generates a content {@link Uri} used to receive updates on simInfo change
* on the given subscriptionId
@@ -1298,6 +1337,8 @@
private final Context mContext;
+ private static boolean sIsSubscriptionManagerServiceEnabled = false;
+
// Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing
// the Context and subId.
private static final Map<Pair<Context, Integer>, Resources> sResourcesCache =
@@ -1383,6 +1424,19 @@
public SubscriptionManager(Context context) {
if (DBG) logd("SubscriptionManager created");
mContext = context;
+
+ sIsSubscriptionManagerServiceEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_using_subscription_manager_service);
+ }
+
+ /**
+ * @return {@code true} if the new subscription manager service is used. This is temporary and
+ * will be removed before Android 14 release.
+ *
+ * @hide
+ */
+ public static boolean isSubscriptionManagerServiceEnabled() {
+ return sIsSubscriptionManagerServiceEnabled;
}
private NetworkPolicyManager getNetworkPolicyManager() {
@@ -1712,8 +1766,7 @@
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible
- * to the calling app are returned.
+ * {@link TelephonyManager#hasCarrierPrivileges}).
*
* @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
* <ul>
@@ -1731,7 +1784,6 @@
* </li>
* </ul>
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
return getActiveSubscriptionInfoList(/* userVisibleonly */true);
@@ -1935,17 +1987,12 @@
}
/**
+ * Get the active subscription count.
*
- * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
- * or that the calling app has carrier privileges (see
- * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include
- * only those subscriptions accessible to the caller.
+ * @return The current number of active subscriptions.
*
- * @return the current number of active subscriptions. There is no guarantee the value
- * returned by this method will be the same as the length of the list returned by
- * {@link #getActiveSubscriptionInfoList}.
+ * @see #getActiveSubscriptionInfoList()
*/
- @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
public int getActiveSubscriptionInfoCount() {
int result = 0;
@@ -2085,7 +2132,7 @@
/**
* Set SIM icon tint color for subscription ID
* @param tint the RGB value of icon tint color of the SIM
- * @param subId the unique Subscritpion ID in database
+ * @param subId the unique subscription ID in database
* @return the number of records updated
* @hide
*/
@@ -2093,7 +2140,7 @@
public int setIconTint(@ColorInt int tint, int subId) {
if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
return setSubscriptionPropertyHelper(subId, "setIconTint",
- (iSub)-> iSub.setIconTint(tint, subId)
+ (iSub)-> iSub.setIconTint(subId, tint)
);
}
@@ -2158,6 +2205,7 @@
* subscriptionId doesn't have an associated slot index.
*/
public static int getSlotIndex(int subscriptionId) {
+ if (isSubscriptionManagerServiceEnabled()) return sGetSlotIndexCache.query(subscriptionId);
return sSlotIndexCache.query(subscriptionId);
}
@@ -2207,12 +2255,14 @@
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+ if (isSubscriptionManagerServiceEnabled()) return sGetSubIdCache.query(slotIndex);
return sSubIdCache.query(slotIndex);
}
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public static int getPhoneId(int subId) {
+ if (isSubscriptionManagerServiceEnabled()) return sGetPhoneIdCache.query(subId);
return sPhoneIdCache.query(subId);
}
@@ -2234,6 +2284,7 @@
* @return the "system" default subscription id.
*/
public static int getDefaultSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) return sGetDefaultSubIdCache.query(null);
return sDefaultSubIdCache.query(null);
}
@@ -2322,6 +2373,7 @@
* @return the default SMS subscription Id.
*/
public static int getDefaultSmsSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) return sGetDefaultSmsSubIdCache.query(null);
return sDefaultSmsSubIdCache.query(null);
}
@@ -2356,6 +2408,7 @@
* @return the default data subscription Id.
*/
public static int getDefaultDataSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) return sGetDefaultDataSubIdCache.query(null);
return sDefaultDataSubIdCache.query(null);
}
@@ -3819,6 +3872,9 @@
* SubscriptionManager.INVALID_SUBSCRIPTION_ID if not.
*/
public static int getActiveDataSubscriptionId() {
+ if (isSubscriptionManagerServiceEnabled()) {
+ return sGetActiveDataSubscriptionIdCache.query(null);
+ }
return sActiveDataSubIdCache.query(null);
}
@@ -3862,6 +3918,11 @@
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SLOT_INDEX_PROPERTY);
}
+ /** @hide */
+ public static void invalidateSubscriptionManagerServiceCaches() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_SUBSCRIPTION_MANAGER_SERVICE_PROPERTY);
+ }
+
/**
* Allows a test process to disable client-side caching operations.
*
@@ -3873,7 +3934,16 @@
sActiveDataSubIdCache.disableLocal();
sDefaultSmsSubIdCache.disableLocal();
sSlotIndexCache.disableLocal();
+ sSubIdCache.disableLocal();
sPhoneIdCache.disableLocal();
+
+ sGetDefaultSubIdCache.disableLocal();
+ sGetDefaultDataSubIdCache.disableLocal();
+ sGetActiveDataSubscriptionIdCache.disableLocal();
+ sGetDefaultSmsSubIdCache.disableLocal();
+ sGetSlotIndexCache.disableLocal();
+ sGetSubIdCache.disableLocal();
+ sGetPhoneIdCache.disableLocal();
}
/**
@@ -3886,7 +3956,16 @@
sActiveDataSubIdCache.clear();
sDefaultSmsSubIdCache.clear();
sSlotIndexCache.clear();
+ sSubIdCache.clear();
sPhoneIdCache.clear();
+
+ sGetDefaultSubIdCache.clear();
+ sGetDefaultDataSubIdCache.clear();
+ sGetActiveDataSubscriptionIdCache.clear();
+ sGetDefaultSmsSubIdCache.clear();
+ sGetSlotIndexCache.clear();
+ sGetSubIdCache.clear();
+ sGetPhoneIdCache.clear();
}
/**
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e61d1e6..b18eaa53 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -75,7 +75,6 @@
CANCEL_REASON_TIMEOUT,
CANCEL_REASON_PPR_NOT_ALLOWED
})
- /** @hide */
public @interface CancelReason {
}
@@ -105,7 +104,6 @@
RESET_OPTION_DELETE_FIELD_LOADED_TEST_PROFILES,
RESET_OPTION_RESET_DEFAULT_SMDP_ADDRESS
})
- /** @hide */
public @interface ResetOption {
}
diff --git a/telephony/java/android/telephony/euicc/EuiccNotification.java b/telephony/java/android/telephony/euicc/EuiccNotification.java
index c348cff..be0048f 100644
--- a/telephony/java/android/telephony/euicc/EuiccNotification.java
+++ b/telephony/java/android/telephony/euicc/EuiccNotification.java
@@ -44,7 +44,6 @@
EVENT_DISABLE,
EVENT_DELETE
})
- /** @hide */
public @interface Event {}
/** A profile is downloaded and installed. */
diff --git a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
index d5a05ae..1c6b6b6 100644
--- a/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
+++ b/telephony/java/android/telephony/euicc/EuiccRulesAuthTable.java
@@ -42,7 +42,6 @@
@IntDef(flag = true, prefix = { "POLICY_RULE_FLAG_" }, value = {
POLICY_RULE_FLAG_CONSENT_REQUIRED
})
- /** @hide */
public @interface PolicyRuleFlag {}
/** User consent is required to install the profile. */
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 5173405..e9cea68 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -135,11 +135,11 @@
/**
* Set SIM icon tint color by simInfo index
- * @param tint the icon tint color of the SIM
* @param subId the unique SubscriptionInfo index in database
+ * @param tint the icon tint color of the SIM
* @return the number of records updated
*/
- int setIconTint(int tint, int subId);
+ int setIconTint(int subId, int tint);
/**
* Set display name by simInfo index with name source
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index a7d6a01..84781b4 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -19,6 +19,8 @@
<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" />
+ <!-- Ensure output directory is empty at the start -->
+ <option name="run-command" value="rm -rf /sdcard/flicker" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 3f6a75d..c100a9c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -20,7 +20,7 @@
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.Assume
@@ -34,12 +34,12 @@
abstract class BaseTest
@JvmOverloads
constructor(
- protected val testSpec: FlickerTestParameter,
+ protected val flicker: FlickerTest,
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
) {
init {
- testSpec.setIsTablet(
+ flicker.scenario.setIsTablet(
WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
.currentState
.wmState
@@ -58,13 +58,13 @@
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
- setup { testSpec.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+ setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
transition()
}
}
/** Checks that all parts of the screen are covered during the transition */
- @Presubmit @Test open fun entireScreenCovered() = testSpec.entireScreenCovered()
+ @Presubmit @Test open fun entireScreenCovered() = flicker.entireScreenCovered()
/**
* Checks that the [ComponentNameMatcher.NAV_BAR] layer is visible during the whole transition
@@ -74,8 +74,8 @@
@Presubmit
@Test
open fun navBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -87,8 +87,8 @@
@Presubmit
@Test
open fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
/**
@@ -99,8 +99,8 @@
@Presubmit
@Test
open fun navBarWindowIsAlwaysVisible() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsAlwaysVisible()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsAlwaysVisible()
}
/**
@@ -112,8 +112,8 @@
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtStartAndEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -124,8 +124,8 @@
@Presubmit
@Test
open fun taskBarWindowIsAlwaysVisible() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarWindowIsAlwaysVisible()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsAlwaysVisible()
}
/**
@@ -134,8 +134,7 @@
*/
@Presubmit
@Test
- open fun statusBarLayerIsVisibleAtStartAndEnd() =
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ open fun statusBarLayerIsVisibleAtStartAndEnd() = flicker.statusBarLayerIsVisibleAtStartAndEnd()
/**
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
@@ -143,7 +142,7 @@
*/
@Presubmit
@Test
- open fun statusBarLayerPositionAtStartAndEnd() = testSpec.statusBarLayerPositionAtStartAndEnd()
+ open fun statusBarLayerPositionAtStartAndEnd() = flicker.statusBarLayerPositionAtStartAndEnd()
/**
* Checks that the [ComponentNameMatcher.STATUS_BAR] window is visible during the whole
@@ -151,7 +150,7 @@
*/
@Presubmit
@Test
- open fun statusBarWindowIsAlwaysVisible() = testSpec.statusBarWindowIsAlwaysVisible()
+ open fun statusBarWindowIsAlwaysVisible() = flicker.statusBarWindowIsAlwaysVisible()
/**
* Checks that all layers that are visible on the trace, are visible for at least 2 consecutive
@@ -160,7 +159,7 @@
@Presubmit
@Test
open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
+ flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry() }
}
/**
@@ -170,7 +169,7 @@
@Presubmit
@Test
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ flicker.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
}
open fun cujCompleted() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index bbffd08..f9a245a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -27,7 +27,7 @@
* Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
* WM trace entries
*/
-fun FlickerTestParameter.statusBarWindowIsAlwaysVisible() {
+fun FlickerTest.statusBarWindowIsAlwaysVisible() {
assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.STATUS_BAR) }
}
@@ -35,7 +35,7 @@
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows in all WM
* trace entries
*/
-fun FlickerTestParameter.navBarWindowIsAlwaysVisible() {
+fun FlickerTest.navBarWindowIsAlwaysVisible() {
assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -43,7 +43,7 @@
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start and end of the WM trace
*/
-fun FlickerTestParameter.navBarWindowIsVisibleAtStartAndEnd() {
+fun FlickerTest.navBarWindowIsVisibleAtStartAndEnd() {
this.navBarWindowIsVisibleAtStart()
this.navBarWindowIsVisibleAtEnd()
}
@@ -52,7 +52,7 @@
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start of the WM trace
*/
-fun FlickerTestParameter.navBarWindowIsVisibleAtStart() {
+fun FlickerTest.navBarWindowIsVisibleAtStart() {
assertWmStart { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -60,7 +60,7 @@
* Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the end
* of the WM trace
*/
-fun FlickerTestParameter.navBarWindowIsVisibleAtEnd() {
+fun FlickerTest.navBarWindowIsVisibleAtEnd() {
assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR) }
}
@@ -68,7 +68,7 @@
* Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
* trace entries
*/
-fun FlickerTestParameter.taskBarWindowIsAlwaysVisible() {
+fun FlickerTest.taskBarWindowIsAlwaysVisible() {
assertWm { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -76,7 +76,7 @@
* Checks that [ComponentNameMatcher.TASK_BAR] window is visible and above the app windows in all WM
* trace entries
*/
-fun FlickerTestParameter.taskBarWindowIsVisibleAtEnd() {
+fun FlickerTest.taskBarWindowIsVisibleAtEnd() {
assertWmEnd { this.isAboveAppWindowVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -90,7 +90,7 @@
* @param allStates if all states should be checked, othersie, just initial and final
*/
@JvmOverloads
-fun FlickerTestParameter.entireScreenCovered(allStates: Boolean = true) {
+fun FlickerTest.entireScreenCovered(allStates: Boolean = true) {
if (allStates) {
assertLayers {
this.invoke("entireScreenCovered") { entry ->
@@ -114,19 +114,19 @@
}
/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start of the SF trace */
-fun FlickerTestParameter.navBarLayerIsVisibleAtStart() {
+fun FlickerTest.navBarLayerIsVisibleAtStart() {
assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
/** Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the end of the SF trace */
-fun FlickerTestParameter.navBarLayerIsVisibleAtEnd() {
+fun FlickerTest.navBarLayerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
/**
* Checks that [ComponentNameMatcher.NAV_BAR] layer is visible at the start and end of the SF trace
*/
-fun FlickerTestParameter.navBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.navBarLayerIsVisibleAtStartAndEnd() {
this.navBarLayerIsVisibleAtStart()
this.navBarLayerIsVisibleAtEnd()
}
@@ -134,18 +134,18 @@
/**
* Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start and end of the SF trace
*/
-fun FlickerTestParameter.taskBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.taskBarLayerIsVisibleAtStartAndEnd() {
this.taskBarLayerIsVisibleAtStart()
this.taskBarLayerIsVisibleAtEnd()
}
/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the start of the SF trace */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtStart() {
+fun FlickerTest.taskBarLayerIsVisibleAtStart() {
assertLayersStart { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
/** Checks that [ComponentNameMatcher.TASK_BAR] layer is visible at the end of the SF trace */
-fun FlickerTestParameter.taskBarLayerIsVisibleAtEnd() {
+fun FlickerTest.taskBarLayerIsVisibleAtEnd() {
assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
@@ -153,7 +153,7 @@
* Checks that [ComponentNameMatcher.STATUS_BAR] layer is visible at the start and end of the SF
* trace
*/
-fun FlickerTestParameter.statusBarLayerIsVisibleAtStartAndEnd() {
+fun FlickerTest.statusBarLayerIsVisibleAtStartAndEnd() {
assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
}
@@ -162,12 +162,14 @@
* Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start of
* the SF trace
*/
-fun FlickerTestParameter.navBarLayerPositionAtStart() {
+fun FlickerTest.navBarLayerPositionAtStart() {
assertLayersStart {
val display =
this.entry.displays.firstOrNull { !it.isVirtual } ?: error("There is no display!")
this.visibleRegion(ComponentNameMatcher.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
+ .coversExactly(
+ WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
+ )
}
}
@@ -175,13 +177,15 @@
* Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the end of
* the SF trace
*/
-fun FlickerTestParameter.navBarLayerPositionAtEnd() {
+fun FlickerTest.navBarLayerPositionAtEnd() {
assertLayersEnd {
val display =
this.entry.displays.minByOrNull { it.id }
?: throw RuntimeException("There is no display!")
this.visibleRegion(ComponentNameMatcher.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
+ .coversExactly(
+ WindowUtils.getNavigationBarPosition(display, scenario.isGesturalNavigation)
+ )
}
}
@@ -189,7 +193,7 @@
* Asserts that the [ComponentNameMatcher.NAV_BAR] layer is at the correct position at the start and
* end of the SF trace
*/
-fun FlickerTestParameter.navBarLayerPositionAtStartAndEnd() {
+fun FlickerTest.navBarLayerPositionAtStartAndEnd() {
navBarLayerPositionAtStart()
navBarLayerPositionAtEnd()
}
@@ -198,7 +202,7 @@
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
* of the SF trace
*/
-fun FlickerTestParameter.statusBarLayerPositionAtStart() {
+fun FlickerTest.statusBarLayerPositionAtStart() {
assertLayersStart {
val display =
this.entry.displays.minByOrNull { it.id }
@@ -212,7 +216,7 @@
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
* the SF trace
*/
-fun FlickerTestParameter.statusBarLayerPositionAtEnd() {
+fun FlickerTest.statusBarLayerPositionAtEnd() {
assertLayersEnd {
val display =
this.entry.displays.minByOrNull { it.id }
@@ -226,7 +230,7 @@
* Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
* and end of the SF trace
*/
-fun FlickerTestParameter.statusBarLayerPositionAtStartAndEnd() {
+fun FlickerTest.statusBarLayerPositionAtStartAndEnd() {
statusBarLayerPositionAtStart()
statusBarLayerPositionAtEnd()
}
@@ -235,9 +239,7 @@
* Asserts that the visibleRegion of the [ComponentNameMatcher.SNAPSHOT] layer can cover the
* visibleRegion of the given app component exactly
*/
-fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
- component: IComponentNameMatcher
-) {
+fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: IComponentNameMatcher) {
assertLayers {
invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
val snapshotLayers =
@@ -291,7 +293,7 @@
* otherwise we won't and the layer must appear immediately.
* ```
*/
-fun FlickerTestParameter.replacesLayer(
+fun FlickerTest.replacesLayer(
originalLayer: IComponentNameMatcher,
newLayer: IComponentNameMatcher,
ignoreEntriesWithRotationLayer: Boolean = false,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
index 4cf6691..b7bdeeb7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/ActivityEmbeddingTestBase.kt
@@ -17,11 +17,11 @@
package com.android.server.wm.flicker.activityembedding
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import org.junit.Before
-abstract class ActivityEmbeddingTestBase(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class ActivityEmbeddingTestBase(flicker: FlickerTest) : BaseTest(flicker) {
val testApp = ActivityEmbeddingAppHelper(instrumentation)
@Before
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
index b23fb5a..ea67729 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplit.kt
@@ -17,14 +17,12 @@
package com.android.server.wm.flicker.activityembedding
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,8 +39,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenActivityEmbeddingPlaceholderSplit(testSpec: FlickerTestParameter) :
- ActivityEmbeddingTestBase(testSpec) {
+class OpenActivityEmbeddingPlaceholderSplit(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -60,7 +58,7 @@
@Presubmit
@Test
fun mainActivityBecomesInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
.then()
.isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
@@ -70,12 +68,12 @@
@Presubmit
@Test
fun placeholderSplitBecomesVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
.then()
.isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_PRIMARY_COMPONENT)
}
- testSpec.assertLayers {
+ flicker.assertLayers {
isInvisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
.then()
.isVisible(ActivityEmbeddingAppHelper.PLACEHOLDER_SECONDARY_COMPONENT)
@@ -142,21 +140,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index b16bfe0..d891714 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -18,11 +18,11 @@
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,7 +69,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
+class CloseAppBackButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -89,13 +89,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 78d0860..cc8ef1d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -18,11 +18,11 @@
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,7 +69,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransition(testSpec) {
+class CloseAppHomeButtonTest(flicker: FlickerTest) : CloseAppTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -91,16 +91,11 @@
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
companion object {
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
- */
+ /** Creates the test configurations. */
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 5bb227f..23503d2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -18,8 +18,8 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
@@ -28,15 +28,15 @@
import org.junit.Test
/** Base test class for transitions that close an app back to the launcher screen */
-abstract class CloseAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class CloseAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
}
@@ -48,7 +48,7 @@
@Presubmit
@Test
open fun launcherReplacesAppWindowAsTopWindow() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowOnTop(LAUNCHER) }
}
/**
@@ -58,17 +58,17 @@
@Presubmit
@Test
open fun launcherWindowBecomesVisible() {
- testSpec.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
+ flicker.assertWm { this.isAppWindowNotOnTop(LAUNCHER).then().isAppWindowOnTop(LAUNCHER) }
}
/** Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible */
@Presubmit
@Test
open fun launcherLayerReplacesApp() {
- testSpec.replacesLayer(
+ flicker.replacesLayer(
testApp,
LAUNCHER,
- ignoreEntriesWithRotationLayer = testSpec.isLandscapeOrSeascapeAtStart
+ ignoreEntriesWithRotationLayer = flicker.scenario.isLandscapeOrSeascapeAtStart
)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index 48e1e64..368cc56 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import android.util.Log
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -37,10 +35,8 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.ActivityEmbedding.MainActivity.LABEL,
- component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT,
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = MAIN_ACTIVITY_COMPONENT
+) : StandardAppHelper(instr, launcherName, component) {
/**
* Clicks the button to launch the placeholder primary activity, which should launch the
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
index efb92f2..18563ff 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/AssistantAppHelper.kt
@@ -25,45 +25,52 @@
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.Assert.assertNotNull
-class AssistantAppHelper @JvmOverloads constructor(
+class AssistantAppHelper
+@JvmOverloads
+constructor(
val instr: Instrumentation,
val component: ComponentName = ActivityOptions.ASSISTANT_SERVICE_COMPONENT_NAME,
) {
protected val uiDevice: UiDevice = UiDevice.getInstance(instr)
- protected val defaultAssistant: String? = Settings.Secure.getString(
- instr.targetContext.contentResolver,
- Settings.Secure.ASSISTANT)
- protected val defaultVoiceInteractionService: String? = Settings.Secure.getString(
- instr.targetContext.contentResolver,
- Settings.Secure.VOICE_INTERACTION_SERVICE)
+ protected val defaultAssistant: String? =
+ Settings.Secure.getString(instr.targetContext.contentResolver, Settings.Secure.ASSISTANT)
+ protected val defaultVoiceInteractionService: String? =
+ Settings.Secure.getString(
+ instr.targetContext.contentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE
+ )
fun setDefaultAssistant() {
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.VOICE_INTERACTION_SERVICE,
- component.flattenToString())
+ component.flattenToString()
+ )
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.ASSISTANT,
- component.flattenToString())
+ component.flattenToString()
+ )
}
fun resetDefaultAssistant() {
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.VOICE_INTERACTION_SERVICE,
- defaultVoiceInteractionService)
+ defaultVoiceInteractionService
+ )
Settings.Secure.putString(
instr.targetContext.contentResolver,
Settings.Secure.ASSISTANT,
- defaultAssistant)
+ defaultAssistant
+ )
}
/**
* Open Assistance UI.
*
- * @param longpress open the UI by long pressing power button.
- * Otherwise open the UI through vioceinteraction shell command directly.
+ * @param longpress open the UI by long pressing power button. Otherwise open the UI through
+ * vioceinteraction shell command directly.
*/
@JvmOverloads
fun openUI(longpress: Boolean = false) {
@@ -72,9 +79,11 @@
} else {
uiDevice.executeShellCommand("cmd voiceinteraction show")
}
- val ui = uiDevice.wait(
- Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
- FIND_TIMEOUT)
+ val ui =
+ uiDevice.wait(
+ Until.findObject(By.res(ActivityOptions.FLICKER_APP_PACKAGE, "vis_frame")),
+ FIND_TIMEOUT
+ )
assertNotNull("Can't find Assistant UI after long pressing power button.", ui)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
index 4340bd7..05b50f0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FixedOrientationAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
instr: Instrumentation,
launcherName: String = ActivityOptions.PortraitOnlyActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.PortraitOnlyActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
index 73cb862..2ae8e1d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/FlickerExtensions.kt
@@ -18,15 +18,16 @@
package com.android.server.wm.flicker.helpers
-import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.IFlickerTestData
import com.android.server.wm.flicker.rules.ChangeDisplayOrientationRule
+import com.android.server.wm.traces.common.service.PlatformConsts
/**
* Changes the device [rotation] and wait for the rotation animation to complete
*
* @param rotation New device rotation
*/
-fun Flicker.setRotation(rotation: Int) =
+fun IFlickerTestData.setRotation(rotation: PlatformConsts.Rotation) =
ChangeDisplayOrientationRule.setRotation(
rotation,
instrumentation,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
index d45315e..d583bba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GameAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.Until
@@ -32,10 +30,8 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Game.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy,
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Game.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
/**
* Swipes down in the mock game app.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index ca5b2af..b7eea1b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -26,6 +26,7 @@
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Condition
import com.android.server.wm.traces.common.DeviceStateDump
+import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import java.util.regex.Pattern
@@ -34,7 +35,7 @@
@JvmOverloads
constructor(
instr: Instrumentation,
- private val rotation: Int,
+ private val rotation: PlatformConsts.Rotation,
private val imePackageName: String = IME_PACKAGE,
launcherName: String = ActivityOptions.Ime.AutoFocusActivity.LABEL,
component: ComponentNameMatcher =
@@ -63,7 +64,7 @@
} else {
getPackage()
}
- launcherStrategy.launch(appName, expectedPackage)
+ open(expectedPackage)
}
fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index cefbf18..3bb7f4e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -31,10 +29,8 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Ime.Default.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Ime.Default.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
/**
* Opens the IME and wait for it to be displayed
*
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
index 1502ad5..69d6a47 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
instr: Instrumentation,
launcherName: String = ActivityOptions.Ime.StateInitializeActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.Ime.StateInitializeActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
index f00904b..d0935ef 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/MailAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.UiObject2
@@ -32,10 +30,8 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Mail.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Mail.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
fun openMail(rowIdx: Int) {
val rowSel =
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index 34294a6..8b3fa18 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
@@ -32,10 +30,8 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.LaunchNewTask.LABEL,
- component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.LaunchNewTask.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
fun openNewTask(device: UiDevice, wmHelper: WindowManagerStateHelper) {
val button =
device.wait(Until.findObject(By.res(getPackage(), "launch_new_task")), FIND_TIMEOUT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
index bbb782d..992a1a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NonResizeableAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
instr: Instrumentation,
launcherName: String = ActivityOptions.NonResizeableActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.NonResizeableActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index a3e32e5..c29c752 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -31,10 +29,8 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.Notification.LABEL,
- component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ component: ComponentNameMatcher = ActivityOptions.Notification.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
fun postNotification(wmHelper: WindowManagerStateHelper) {
val button =
uiDevice.wait(Until.findObject(By.res(getPackage(), "post_notification")), FIND_TIMEOUT)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 19ee09a..8fe6aac 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -92,8 +92,12 @@
// if the distance per step is less than 1, carry out the animation in two steps
gestureHelper.pinch(
- Tuple(initLeftX, yCoord), Tuple(initRightX, yCoord),
- Tuple(finalLeftX, yCoord), Tuple(finalRightX, yCoord), adjustedSteps)
+ Tuple(initLeftX, yCoord),
+ Tuple(initRightX, yCoord),
+ Tuple(finalLeftX, yCoord),
+ Tuple(finalRightX, yCoord),
+ adjustedSteps
+ )
waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
index c904352..c51754c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SeamlessRotationAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
instr: Instrumentation,
launcherName: String = ActivityOptions.SeamlessRotation.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.SeamlessRotation.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
index de152cb5..9318f20 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ShowWhenLockedAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -29,7 +27,5 @@
instr: Instrumentation,
launcherName: String = ActivityOptions.ShowWhenLockedActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ ActivityOptions.ShowWhenLockedActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
index e415990..b46ff2c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/SimpleAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -28,7 +26,5 @@
constructor(
instr: Instrumentation,
launcherName: String = ActivityOptions.SimpleActivity.LABEL,
- component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
+ component: ComponentNameMatcher = ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index 1330190..720d962 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -17,8 +17,6 @@
package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
@@ -33,10 +31,8 @@
instr: Instrumentation,
launcherName: String = ActivityOptions.LaunchNewActivity.LABEL,
component: ComponentNameMatcher =
- ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent(),
- launcherStrategy: ILauncherStrategy =
- LauncherStrategyFactory.getInstance(instr).launcherStrategy
-) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
+ ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component) {
private val secondActivityComponent =
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 1a49595..c735be0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -17,16 +17,15 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,8 +48,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class CloseImeAutoOpenWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -62,43 +61,37 @@
@Presubmit
@Test
fun imeAppWindowIsAlwaysVisible() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
@Presubmit
@Test
fun imeLayerVisibleStart() {
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun imeLayerInvisibleEnd() {
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // b/190352379 (IME doesn't show on app launch in 90 degrees)
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // b/190352379 (IME doesn't show on app launch in 90 degrees)
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 463efe8..4024f56 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -17,16 +17,15 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,8 +48,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeAutoOpenWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class CloseImeAutoOpenWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -68,43 +67,37 @@
@Presubmit
@Test
fun imeAppWindowBecomesInvisible() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp).then().isAppWindowNotOnTop(testApp) }
}
@Presubmit
@Test
fun imeLayerVisibleStart() {
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun imeLayerInvisibleEnd() {
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.IME) }
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
fun imeAppLayerBecomesInvisible() {
- testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- // b/190352379 (IME doesn't show on app launch in 90 degrees)
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // b/190352379 (IME doesn't show on app launch in 90 degrees)
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 91d9a1f..c72405c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -17,17 +17,16 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeEditorPopupDialogTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeEditorPopupDialogTest(flicker: FlickerTest) : BaseTest(flicker) {
private val imeTestApp = ImeEditorPopupDialogAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -59,14 +58,12 @@
}
}
- @Presubmit
- @Test
- fun imeWindowBecameInvisible() = testSpec.imeWindowBecomesInvisible()
+ @Presubmit @Test fun imeWindowBecameInvisible() = flicker.imeWindowBecomesInvisible()
@Presubmit
@Test
fun imeLayerAndImeSnapshotVisibleOnScreen() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.IME)
.then()
.isVisible(ComponentNameMatcher.IME_SNAPSHOT)
@@ -79,7 +76,7 @@
@Presubmit
@Test
fun imeSnapshotAssociatedOnAppVisibleRegion() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("imeSnapshotAssociatedOnAppVisibleRegion") {
val imeSnapshotLayers =
it.subjects.filter { subject ->
@@ -106,16 +103,10 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- ),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index ef42766..afc5f65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -21,11 +21,11 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
@@ -43,7 +43,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeWindowToAppTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -60,7 +60,7 @@
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm {
+ flicker.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
listOf(
ComponentNameMatcher.IME,
@@ -75,31 +75,31 @@
@Presubmit
@Test
override fun navBarLayerPositionAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
@FlakyTest
@Test
fun navBarLayerPositionAtStartAndEndLandscapeOrSeascapeAtStart() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- testSpec.navBarLayerPositionAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ flicker.navBarLayerPositionAtStartAndEnd()
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
@Presubmit
@Test
fun imeAppWindowIsAlwaysVisible() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
@Test
@@ -115,8 +115,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index c92fce3..aedf965 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -18,16 +18,15 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,7 +41,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class CloseImeWindowToHomeTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class CloseImeWindowToHomeTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -63,7 +62,7 @@
@Presubmit
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm {
+ flicker.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
listOf(
ComponentNameMatcher.IME,
@@ -78,27 +77,27 @@
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
listOf(ComponentNameMatcher.IME, ComponentNameMatcher.SPLASH_SCREEN)
)
}
}
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
- @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+ @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible()
@Presubmit
@Test
fun imeAppWindowBecomesInvisible() {
- testSpec.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
+ flicker.assertWm { this.isAppWindowVisible(testApp).then().isAppWindowInvisible(testApp) }
}
@Presubmit
@Test
fun imeAppLayerBecomesInvisible() {
- testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
@Test
@@ -115,16 +114,10 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
index e0c5edc..3edc15f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CommonAssertions.kt
@@ -18,22 +18,22 @@
package com.android.server.wm.flicker.ime
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.traces.common.ComponentNameMatcher
-fun FlickerTestParameter.imeLayerBecomesVisible() {
+fun FlickerTest.imeLayerBecomesVisible() {
assertLayers {
this.isInvisible(ComponentNameMatcher.IME).then().isVisible(ComponentNameMatcher.IME)
}
}
-fun FlickerTestParameter.imeLayerBecomesInvisible() {
+fun FlickerTest.imeLayerBecomesInvisible() {
assertLayers {
this.isVisible(ComponentNameMatcher.IME).then().isInvisible(ComponentNameMatcher.IME)
}
}
-fun FlickerTestParameter.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
+fun FlickerTest.imeWindowIsAlwaysVisible(rotatesScreen: Boolean = false) {
if (rotatesScreen) {
assertWm {
this.isNonAppWindowVisible(ComponentNameMatcher.IME)
@@ -47,7 +47,7 @@
}
}
-fun FlickerTestParameter.imeWindowBecomesVisible() {
+fun FlickerTest.imeWindowBecomesVisible() {
assertWm {
this.isNonAppWindowInvisible(ComponentNameMatcher.IME)
.then()
@@ -55,7 +55,7 @@
}
}
-fun FlickerTestParameter.imeWindowBecomesInvisible() {
+fun FlickerTest.imeWindowBecomesInvisible() {
assertWm {
this.isNonAppWindowVisible(ComponentNameMatcher.IME)
.then()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index 073da4f..da3c62d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -18,19 +18,18 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import android.view.WindowInsets.Type.ime
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
@@ -47,8 +46,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeAndDialogThemeAppTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class LaunchAppShowImeAndDialogThemeAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -75,42 +74,36 @@
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
- @Presubmit @Test fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+ /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
+ @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
- /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
+ /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */
@Presubmit
@Test
fun imeLayerExistsEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
}
- /** Checks that [ComponentMatcher.IME_SNAPSHOT] layer is invisible always. */
+ /** Checks that [ComponentNameMatcher.IME_SNAPSHOT] layer is invisible always. */
@Presubmit
@Test
fun imeSnapshotNotVisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.IME_SNAPSHOT) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index a93f176..4891901 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -17,18 +17,17 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -73,15 +72,15 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class LaunchAppShowImeOnStartTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class LaunchAppShowImeOnStartTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
private val initializeApp = ImeStateInitializeHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
initializeApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown {
initializeApp.exit(wmHelper)
@@ -93,45 +92,39 @@
}
}
- /** Checks that [ComponentMatcher.IME] window becomes visible during the transition */
- @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+ /** Checks that [ComponentNameMatcher.IME] window becomes visible during the transition */
+ @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
- /** Checks that [ComponentMatcher.IME] layer becomes visible during the transition */
- @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ /** Checks that [ComponentNameMatcher.IME] layer becomes visible during the transition */
+ @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
- /** Checks that [ComponentMatcher.IME] layer is invisible at the start of the transition */
+ /** Checks that [ComponentNameMatcher.IME] layer is invisible at the start of the transition */
@Presubmit
@Test
fun imeLayerNotExistsStart() {
- testSpec.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { this.isInvisible(ComponentNameMatcher.IME) }
}
- /** Checks that [ComponentMatcher.IME] layer is visible at the end of the transition */
+ /** Checks that [ComponentNameMatcher.IME] layer is visible at the end of the transition */
@Presubmit
@Test
fun imeLayerExistsEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.IME) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index 7d7953b..33e9574 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -19,17 +19,16 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -47,7 +46,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowAndCloseTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class OpenImeWindowAndCloseTest(flicker: FlickerTest) : BaseTest(flicker) {
private val simpleApp = SimpleAppHelper(instrumentation)
private val testApp = ImeAppHelper(instrumentation)
@@ -62,9 +61,9 @@
teardown { simpleApp.exit(wmHelper) }
}
- @Presubmit @Test fun imeWindowBecomesInvisible() = testSpec.imeWindowBecomesInvisible()
+ @Presubmit @Test fun imeWindowBecomesInvisible() = flicker.imeWindowBecomesInvisible()
- @Presubmit @Test fun imeLayerBecomesInvisible() = testSpec.imeLayerBecomesInvisible()
+ @Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
@Presubmit
@Test
@@ -91,16 +90,10 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 3e18d59..197564a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -19,17 +19,16 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -46,9 +45,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowFromFixedOrientationAppTest(testSpec: FlickerTestParameter) :
- BaseTest(testSpec) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class OpenImeWindowFromFixedOrientationAppTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -63,61 +61,58 @@
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
}
transitions {
- // Bring the exist IME activity to the front in landscape mode device rotation.
- setRotation(Surface.ROTATION_90)
+ // Bring the existing IME activity to the front in landscape mode device rotation.
+ setRotation(PlatformConsts.Rotation.ROTATION_90)
imeTestApp.launchViaIntent(wmHelper)
}
teardown { imeTestApp.exit(wmHelper) }
}
/** {@inheritDoc} */
- @Presubmit
+ @Postsubmit
@Test
override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+
/** {@inheritDoc} */
@Postsubmit
@Test
override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
- @Presubmit
- @Test
- fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+ @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
- @Presubmit
- @Test
- fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
@Postsubmit
@Test
fun snapshotStartingWindowLayerCoversExactlyOnApp() {
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+ flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
@Presubmit
@Test
fun snapshotStartingWindowLayerCoversExactlyOnApp_ShellTransit() {
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
+ flicker.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp)
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_90),
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_90)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 9919d87..c097511 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -18,15 +18,14 @@
import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,7 +37,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class OpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = ImeAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -60,35 +59,29 @@
layerAlwaysVisible()
}
- @Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
+ @Presubmit @Test fun imeWindowBecomesVisible() = flicker.imeWindowBecomesVisible()
@Presubmit
@Test
fun appWindowAlwaysVisibleOnTop() {
- testSpec.assertWm { this.isAppWindowOnTop(testApp) }
+ flicker.assertWm { this.isAppWindowOnTop(testApp) }
}
- @Presubmit @Test fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ @Presubmit @Test fun imeLayerBecomesVisible() = flicker.imeLayerBecomesVisible()
@Presubmit
@Test
fun layerAlwaysVisible() {
- testSpec.assertLayers { this.isVisible(testApp) }
+ flicker.assertLayers { this.isVisible(testApp) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index 0a7701e..209eb0c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -18,15 +18,13 @@
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -48,8 +46,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenImeWindowToOverViewTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+class OpenImeWindowToOverViewTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -82,7 +80,7 @@
*/
private fun waitNavStatusBarVisibility(stateSync: WindowManagerStateHelper.StateSyncBuilder) {
when {
- testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isTablet ->
+ flicker.scenario.isLandscapeOrSeascapeAtStart && !flicker.scenario.isTablet ->
stateSync.add(WindowManagerConditionsFactory.isStatusBarVisible().negate())
else -> stateSync.withNavOrTaskBarVisible().withStatusBarVisible()
}
@@ -91,25 +89,25 @@
@Presubmit
@Test
fun imeWindowIsAlwaysVisible() {
- testSpec.imeWindowIsAlwaysVisible()
+ flicker.imeWindowIsAlwaysVisible()
}
@Presubmit
@Test
fun navBarLayerIsVisibleAtStartAndEnd3Button() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeFalse(testSpec.isGesturalNavigation)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/** Bars are expected to be hidden while entering overview in landscape (b/227189877) */
@Presubmit
@Test
fun navBarLayerIsVisibleAtStartAndEndGestural() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.navBarLayerIsVisibleAtStartAndEnd()
+ flicker.navBarLayerIsVisibleAtStartAndEnd()
}
/**
@@ -119,12 +117,12 @@
@Presubmit
@Test
fun navBarLayerIsInvisibleInLandscapeGestural() {
- Assume.assumeFalse(testSpec.isTablet)
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.NAV_BAR) }
}
/**
@@ -134,11 +132,11 @@
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapePhone() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/**
@@ -148,35 +146,31 @@
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapeTablet() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isGesturalNavigation)
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun navBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun navBarLayerPositionAtStartAndEnd() {
- }
+ override fun navBarLayerPositionAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun statusBarLayerPositionAtStartAndEnd() {
- }
+ override fun statusBarLayerPositionAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Visibility changes depending on orientation and navigation mode")
- override fun statusBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun statusBarLayerIsVisibleAtStartAndEnd() {}
@Presubmit
@Test
@@ -185,38 +179,38 @@
@Presubmit
@Test
fun statusBarLayerIsVisibleInPortrait() {
- Assume.assumeFalse(testSpec.isLandscapeOrSeascapeAtStart)
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
@Presubmit
@Test
fun statusBarLayerIsInvisibleInLandscapeShell() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeFalse(flicker.scenario.isTablet)
Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
- testSpec.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
@Presubmit
@Test
fun statusBarLayerIsVisibleInLandscapeLegacy() {
- Assume.assumeTrue(testSpec.isLandscapeOrSeascapeAtStart)
- Assume.assumeTrue(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
+ Assume.assumeTrue(flicker.scenario.isTablet)
Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.statusBarLayerIsVisibleAtStartAndEnd()
+ flicker.statusBarLayerIsVisibleAtStartAndEnd()
}
@Presubmit
@Test
fun imeLayerIsVisibleAndAssociatedWithAppWidow() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
isVisible(ComponentNameMatcher.IME)
.visibleRegion(ComponentNameMatcher.IME)
.coversAtMost(isVisible(imeTestApp).visibleRegion(imeTestApp).region)
}
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("imeLayerIsVisibleAndAlignAppWidow") {
val imeVisibleRegion = it.visibleRegion(ComponentNameMatcher.IME)
val appVisibleRegion = it.visibleRegion(imeTestApp)
@@ -232,21 +226,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90),
- supportedNavigationModes =
- listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY,
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index f810fbb..38791a2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -17,17 +17,17 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.Presubmit
-import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,8 +41,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class ReOpenImeWindowTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
- private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+open class ReOpenImeWindowTest(flicker: FlickerTest) : BaseTest(flicker) {
+ private val testApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
@@ -50,7 +50,7 @@
tapl.workspace.switchToOverview().dismissAllTasks()
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
device.pressRecentApps()
wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
}
@@ -68,7 +68,7 @@
// depends on how much of the animation transactions are sent to SF at once
// sometimes this layer appears for 2-3 frames, sometimes for only 1
val recentTaskComponent = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
listOf(
ComponentNameMatcher.SPLASH_SCREEN,
@@ -84,14 +84,14 @@
@Test
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
val component = ComponentNameMatcher("", "RecentTaskScreenshotSurface")
- testSpec.assertWm {
+ flicker.assertWm {
this.visibleWindowsShownMoreThanOneConsecutiveEntry(
ignoreWindows =
- listOf(
- ComponentNameMatcher.SPLASH_SCREEN,
- ComponentNameMatcher.SNAPSHOT,
- component
- )
+ listOf(
+ ComponentNameMatcher.SPLASH_SCREEN,
+ ComponentNameMatcher.SNAPSHOT,
+ component
+ )
)
}
}
@@ -99,16 +99,14 @@
@Presubmit
@Test
fun launcherWindowBecomesInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER)
}
}
- @Presubmit
- @Test
- fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible()
+ @Presubmit @Test fun imeWindowIsAlwaysVisible() = flicker.imeWindowIsAlwaysVisible()
@Presubmit
@Test
@@ -117,19 +115,19 @@
// and exiting overview. Since we log 1x per frame, sometimes the activity visibility
// and the app visibility are updated together, sometimes not, thus ignore activity
// check at the start
- testSpec.assertWm { this.isAppWindowVisible(testApp) }
+ flicker.assertWm { this.isAppWindowVisible(testApp) }
}
@Presubmit
@Test
fun imeLayerBecomesVisible() {
- testSpec.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayers { this.isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun appLayerReplacesLauncher() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -141,9 +139,10 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 0ca6457..a6bbf54 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -19,19 +19,18 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -49,9 +48,9 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Presubmit
-open class SwitchImeWindowsFromGestureNavTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+open class SwitchImeWindowsFromGestureNavTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
- private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, flicker.scenario.startRotation)
@Before
open fun before() {
@@ -63,7 +62,7 @@
setup {
tapl.setExpectedRotationCheckEnabled(false)
tapl.setIgnoreTaskbarVisibility(true)
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
testApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
@@ -143,7 +142,7 @@
@Presubmit
@Test
fun imeAppWindowVisibility() {
- testSpec.assertWm {
+ flicker.assertWm {
isAppWindowVisible(imeTestApp)
.then()
.isAppSnapshotStartingWindowVisibleFor(testApp, isOptional = true)
@@ -159,27 +158,25 @@
@FlakyTest(bugId = 244414110)
@Test
open fun imeLayerIsVisibleWhenSwitchingToImeApp() {
- testSpec.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
- testSpec.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
- testSpec.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersStart { isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersTag(TAG_IME_VISIBLE) { isVisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersEnd { isVisible(ComponentNameMatcher.IME) }
}
@Presubmit
@Test
fun imeLayerIsInvisibleWhenSwitchingToTestApp() {
- testSpec.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
+ flicker.assertLayersTag(TAG_IME_INVISIBLE) { isInvisible(ComponentNameMatcher.IME) }
}
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
private const val TAG_IME_VISIBLE = "imeVisible"
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
index 80ab016..c599b10 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest_ShellTransit.kt
@@ -18,10 +18,11 @@
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
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
+import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -39,16 +40,14 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchImeWindowsFromGestureNavTest_ShellTransit(testSpec: FlickerTestParameter) :
- SwitchImeWindowsFromGestureNavTest(testSpec) {
+class SwitchImeWindowsFromGestureNavTest_ShellTransit(flicker: FlickerTest) :
+ SwitchImeWindowsFromGestureNavTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
}
- @Presubmit
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered()
@Presubmit
@Test
@@ -71,13 +70,13 @@
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index 49bf86d0..5e50b45 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -20,11 +20,11 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -57,13 +57,13 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ActivitiesTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class ActivitiesTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp.launchViaIntent(wmHelper)
}
teardown { testApp.exit(wmHelper) }
@@ -91,7 +91,7 @@
ActivityOptions.LaunchNewActivity.COMPONENT.toFlickerComponent()
val imeAutoFocusActivityComponent =
ActivityOptions.SimpleActivity.COMPONENT.toFlickerComponent()
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(buttonActivityComponent)
.then()
.isAppWindowOnTop(imeAutoFocusActivityComponent)
@@ -108,7 +108,7 @@
@Presubmit
@Test
fun launcherWindowNotOnTop() {
- testSpec.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertWm { this.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) }
}
/**
@@ -117,20 +117,20 @@
@Presubmit
@Test
fun launcherLayerNotVisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
}
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
index d0d7bbb..14a6668 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest.kt
@@ -17,12 +17,12 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.CameraAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -41,8 +41,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppAfterCameraTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppAfterCameraTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
@Before
open fun before() {
Assume.assumeFalse(isShellTransitionsEnabled)
@@ -69,13 +68,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
index 5686965..99574ef 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -18,9 +18,9 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -40,8 +40,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppAfterCameraTest_ShellTransit(testSpec: FlickerTestParameter) :
- OpenAppAfterCameraTest(testSpec) {
+class OpenAppAfterCameraTest_ShellTransit(flicker: FlickerTest) : OpenAppAfterCameraTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 5e6fc21..e0df5be 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -18,13 +18,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,17 +54,16 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIcon(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
- if (testSpec.isTablet) {
- tapl.setExpectedRotation(testSpec.startRotation)
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
} else {
- tapl.setExpectedRotation(Surface.ROTATION_0)
+ tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value)
}
RemoveAllTasksButHomeRule.removeAllTasksButHome()
}
@@ -180,19 +178,16 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- // TAPL fails on landscape mode b/240916028
- .getConfigNonRotationTests(
- supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY
- )
- )
+ fun getParams(): Collection<FlickerTest> {
+ // TAPL fails on landscape mode b/240916028
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_3BUTTON)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index 7576ab9..66af72e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import org.junit.FixMethodOrder
import org.junit.Test
@@ -57,15 +57,14 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppColdTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppColdTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
setup {
removeAllTasksButHome()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
transitions { testApp.launchViaIntent(wmHelper) }
@@ -83,13 +82,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index 23748be..b234ec7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -17,28 +17,27 @@
package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.replacesLayer
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for app launch tests */
-abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
- OpenAppTransition(testSpec) {
+abstract class OpenAppFromLauncherTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
- /** Checks that the focus changes from the [ComponentMatcher.LAUNCHER] to [testApp] */
+ /** Checks that the focus changes from the [ComponentNameMatcher.LAUNCHER] to [testApp] */
@Presubmit
@Test
open fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges("NexusLauncherActivity", testApp.`package`) }
}
/**
- * Checks that [ComponentMatcher.LAUNCHER] layer is visible at the start of the transition, and
- * is replaced by [testApp], which remains visible until the end
+ * Checks that [ComponentNameMatcher.LAUNCHER] layer is visible at the start of the transition,
+ * and is replaced by [testApp], which remains visible until the end
*/
open fun appLayerReplacesLauncher() {
- testSpec.replacesLayer(
+ flicker.replacesLayer(
ComponentNameMatcher.LAUNCHER,
testApp,
ignoreEntriesWithRotationLayer = true,
@@ -48,14 +47,14 @@
}
/**
- * Checks that [ComponentMatcher.LAUNCHER] window is the top window at the start of the
- * transition, and is replaced by a [ComponentMatcher.SNAPSHOT] or
- * [ComponentMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end
+ * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+ * transition, and is replaced by a [ComponentNameMatcher.SNAPSHOT] or
+ * [ComponentNameMatcher.SPLASH_SCREEN], or [testApp], which remains visible until the end
*/
@Presubmit
@Test
open fun appWindowReplacesLauncherAsTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowOnTop(
@@ -68,6 +67,6 @@
@Presubmit
@Test
open fun appWindowAsTopWindowAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 0edbc86..f5f7190 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -20,10 +20,10 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -44,8 +44,8 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Postsubmit
-open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
- OpenAppFromNotificationCold(testSpec) {
+open class OpenAppFromLockNotificationCold(flicker: FlickerTest) :
+ OpenAppFromNotificationCold(flicker) {
override val openingNotificationsFromLockScreen = true
@@ -93,8 +93,9 @@
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
- @Presubmit @Test override fun statusBarLayerPositionAtEnd() =
- super.statusBarLayerPositionAtEnd()
+ @Presubmit
+ @Test
+ override fun statusBarLayerPositionAtEnd() = super.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -119,13 +120,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index 5a7b8b9..fe49c61 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -19,10 +19,10 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
@@ -43,8 +43,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
- OpenAppFromNotificationWarm(testSpec) {
+class OpenAppFromLockNotificationWarm(flicker: FlickerTest) : OpenAppFromNotificationWarm(flicker) {
override val openingNotificationsFromLockScreen = true
@@ -70,7 +69,7 @@
@Test
@Presubmit
fun appWindowBecomesFirstAndOnlyTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.hasNoVisibleAppWindow()
.then()
.isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -85,7 +84,7 @@
@Test
@Presubmit
fun screenLockedStart() {
- testSpec.assertWmStart { isKeyguardShowing() }
+ flicker.assertWmStart { isKeyguardShowing() }
}
/** {@inheritDoc} */
@@ -108,7 +107,7 @@
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
- @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+ @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -133,13 +132,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index 4ee1283..d9a3ad2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -20,12 +20,12 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
@@ -44,8 +44,8 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Postsubmit
-class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) :
- OpenAppFromLockNotificationCold(testSpec) {
+class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) :
+ OpenAppFromLockNotificationCold(flicker) {
private val showWhenLockedApp: ShowWhenLockedAppHelper =
ShowWhenLockedAppHelper(instrumentation)
@@ -74,7 +74,7 @@
@Test
@FlakyTest(bugId = 227143265)
fun showWhenLockedAppWindowBecomesVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.hasNoVisibleAppWindow()
.then()
.isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -86,7 +86,7 @@
@Test
@FlakyTest(bugId = 227143265)
fun showWhenLockedAppLayerBecomesVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(showWhenLockedApp)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -107,20 +107,19 @@
/** {@inheritDoc} */
@FlakyTest(bugId = 209599395)
@Test
- override fun navBarLayerIsVisibleAtStartAndEnd() =
- super.navBarLayerIsVisibleAtStartAndEnd()
+ override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
companion object {
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
index 3cc2390..718c6e9 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
@@ -18,8 +18,8 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.navBarLayerPositionAtEnd
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -28,8 +28,7 @@
import org.junit.Test
/** Base class for app launch tests from lock screen */
-abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
- OpenAppTransition(testSpec) {
+abstract class OpenAppFromLockTransition(flicker: FlickerTest) : OpenAppTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit = {
@@ -46,7 +45,7 @@
@Presubmit
@Test
open fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges("", testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges("", testApp.`package`) }
}
/**
@@ -56,7 +55,7 @@
@FlakyTest(bugId = 203538234)
@Test
open fun appWindowBecomesFirstAndOnlyTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.hasNoVisibleAppWindow()
.then()
.isAppWindowOnTop(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -71,7 +70,7 @@
@Presubmit
@Test
fun screenLockedStart() {
- testSpec.assertLayersStart { isEmpty() }
+ flicker.assertLayersStart { isEmpty() }
}
/** {@inheritDoc} */
@@ -99,16 +98,16 @@
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
override fun taskBarWindowIsAlwaysVisible() {}
- /** Checks the position of the [ComponentMatcher.NAV_BAR] at the end of the transition */
+ /** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
@Presubmit
@Test
open fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtEnd()
}
- /** Checks the position of the [ComponentMatcher.STATUS_BAR] at the end of the transition */
- @Presubmit @Test fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+ /** Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the end of the transition */
+ @Presubmit @Test fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -116,13 +115,13 @@
override fun statusBarLayerIsVisibleAtStartAndEnd() {}
/**
- * Checks that the [ComponentMatcher.STATUS_BAR] layer is visible at the end of the trace
+ * Checks that the [ComponentNameMatcher.STATUS_BAR] layer is visible at the end of the trace
*
* It is not possible to check at the start because the screen is off
*/
@Presubmit
@Test
fun statusBarLayerIsVisibleAtEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 8dd94cd..240e90b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -19,10 +19,10 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
@@ -44,8 +44,8 @@
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Postsubmit
-open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
- OpenAppFromNotificationWarm(testSpec) {
+open class OpenAppFromNotificationCold(flicker: FlickerTest) :
+ OpenAppFromNotificationWarm(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -61,13 +61,9 @@
}
}
- @Postsubmit
- @Test
- override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
+ @Postsubmit @Test override fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
- @Postsubmit
- @Test
- override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
+ @Postsubmit @Test override fun appLayerBecomesVisible() = appLayerBecomesVisible_coldStart()
/** {@inheritDoc} */
@Test
@@ -89,9 +85,7 @@
* Checks the position of the [ComponentNameMatcher.STATUS_BAR] at the start and end of the
* transition
*/
- @Presubmit
- @Test
- open fun statusBarLayerPositionAtEnd() = testSpec.statusBarLayerPositionAtEnd()
+ @Presubmit @Test open fun statusBarLayerPositionAtEnd() = flicker.statusBarLayerPositionAtEnd()
/** {@inheritDoc} */
@Test
@@ -107,13 +101,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index db48b3f..6388a5a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -24,13 +24,13 @@
import android.view.WindowManager
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NotificationAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarLayerIsVisibleAtEnd
import com.android.server.wm.flicker.navBarLayerPositionAtEnd
import com.android.server.wm.flicker.navBarWindowIsVisibleAtEnd
@@ -56,8 +56,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
- OpenAppTransition(testSpec) {
+open class OpenAppFromNotificationWarm(flicker: FlickerTest) : OpenAppTransition(flicker) {
override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
open val openingNotificationsFromLockScreen = false
@@ -67,7 +66,7 @@
get() = {
setup {
device.wakeUpAndGoToHomeScreen()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
testApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withFullScreenApp(testApp).waitForAndVerify()
testApp.postNotification(wmHelper)
@@ -120,19 +119,19 @@
@Presubmit
@Test
open fun notificationAppWindowVisibleAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
+ flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
}
@Presubmit
@Test
open fun notificationAppWindowOnTopAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
@Presubmit
@Test
open fun notificationAppLayerVisibleAtEnd() {
- testSpec.assertLayersEnd { this.isVisible(testApp) }
+ flicker.assertLayersEnd { this.isVisible(testApp) }
}
/**
@@ -144,8 +143,8 @@
@Presubmit
@Test
open fun taskBarWindowIsVisibleAtEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarWindowIsVisibleAtEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarWindowIsVisibleAtEnd()
}
/**
@@ -156,31 +155,31 @@
@Presubmit
@Test
open fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.taskBarLayerIsVisibleAtEnd()
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.taskBarLayerIsVisibleAtEnd()
}
/** Checks the position of the [ComponentNameMatcher.NAV_BAR] at the end of the transition */
@Presubmit
@Test
open fun navBarLayerPositionAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerPositionAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerPositionAtEnd()
}
/** {@inheritDoc} */
@Presubmit
@Test
open fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarLayerIsVisibleAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarLayerIsVisibleAtEnd()
}
@Presubmit
@Test
open fun navBarWindowIsVisibleAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtEnd()
}
/** {@inheritDoc} */
@@ -203,13 +202,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index fd8a38c..9106835 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,13 +19,13 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,8 +59,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppFromOverviewTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
@@ -72,14 +71,14 @@
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
// By default, launcher doesn't rotate on phones, but rotates on tablets
- if (testSpec.isTablet) {
- tapl.setExpectedRotation(testSpec.startRotation)
+ if (flicker.scenario.isTablet) {
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
} else {
- tapl.setExpectedRotation(Surface.ROTATION_0)
+ tapl.setExpectedRotation(PlatformConsts.Rotation.ROTATION_0.value)
}
tapl.workspace.switchToOverview()
wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
transitions {
tapl.overview.currentTask.open()
@@ -109,13 +108,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index 1ecde46..f295ce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -20,14 +20,13 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -63,8 +62,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
- OpenAppFromLockTransition(testSpec) {
+open class OpenAppNonResizeableTest(flicker: FlickerTest) : OpenAppFromLockTransition(flicker) {
override val testApp = NonResizeableAppHelper(instrumentation)
/**
@@ -74,8 +72,8 @@
@FlakyTest(bugId = 227083463)
@Test
fun navBarLayerVisibilityChanges() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertLayers {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertLayers {
this.isInvisible(ComponentNameMatcher.NAV_BAR)
.then()
.isVisible(ComponentNameMatcher.NAV_BAR)
@@ -86,7 +84,7 @@
@Presubmit
@Test
fun appWindowBecomesVisibleAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowVisible(testApp) }
+ flicker.assertWmEnd { this.isAppWindowVisible(testApp) }
}
/**
@@ -96,8 +94,8 @@
@Presubmit
@Test
fun navBarWindowsVisibilityChanges() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertWm {
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertWm {
this.isNonAppWindowInvisible(ComponentNameMatcher.NAV_BAR)
.then()
.isAboveAppWindowVisible(ComponentNameMatcher.NAV_BAR)
@@ -111,8 +109,8 @@
@Presubmit
@Test
fun taskBarLayerIsVisibleAtEnd() {
- Assume.assumeTrue(testSpec.isTablet)
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
+ Assume.assumeTrue(flicker.scenario.isTablet)
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.TASK_BAR) }
}
/**
@@ -123,45 +121,40 @@
@Presubmit
@Test
override fun statusBarLayerIsVisibleAtStartAndEnd() {
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun taskBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun taskBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun navBarLayerIsVisibleAtStartAndEnd() {
- }
+ override fun navBarLayerIsVisibleAtStartAndEnd() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun taskBarWindowIsAlwaysVisible() {
- }
+ override fun taskBarWindowIsAlwaysVisible() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun navBarWindowIsAlwaysVisible() {
- }
+ override fun navBarWindowIsAlwaysVisible() {}
/** {@inheritDoc} */
@Test
@Ignore("Not applicable to this CUJ. Display starts off and app is full screen at the end")
- override fun statusBarWindowIsAlwaysVisible() {
- }
+ override fun statusBarWindowIsAlwaysVisible() {}
/** Checks the [ComponentNameMatcher.NAV_BAR] is visible at the end of the transition */
@Postsubmit
@Test
fun navBarLayerIsVisibleAtEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.assertLayersEnd { this.isVisible(ComponentNameMatcher.NAV_BAR) }
}
/** {@inheritDoc} */
@@ -174,7 +167,7 @@
@Presubmit
@Test
override fun appLayerBecomesVisible() {
- Assume.assumeFalse(testSpec.isTablet)
+ Assume.assumeFalse(flicker.scenario.isTablet)
super.appLayerBecomesVisible()
}
@@ -182,14 +175,12 @@
@FlakyTest(bugId = 227143265)
@Test
fun appLayerBecomesVisibleTablet() {
- Assume.assumeTrue(testSpec.isTablet)
+ Assume.assumeTrue(flicker.scenario.isTablet)
super.appLayerBecomesVisible()
}
/** {@inheritDoc} */
- @FlakyTest
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
+ @FlakyTest @Test override fun entireScreenCovered() = super.entireScreenCovered()
@FlakyTest(bugId = 218470989)
@Test
@@ -210,18 +201,16 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 4fd251a..2adb0b4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -18,8 +18,8 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
@@ -28,15 +28,15 @@
import org.junit.Test
/** Base class for app launch tests */
-abstract class OpenAppTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class OpenAppTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
device.wakeUpAndGoToHomeScreen()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
}
@@ -52,7 +52,7 @@
}
protected fun appLayerBecomesVisible_coldStart() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.notContains(testApp)
.then()
.isInvisible(testApp, isOptional = true)
@@ -66,7 +66,7 @@
}
protected fun appLayerBecomesVisible_warmStart() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(testApp)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -87,7 +87,7 @@
@Presubmit @Test open fun appWindowBecomesVisible() = appWindowBecomesVisible_coldStart()
protected fun appWindowBecomesVisible_coldStart() {
- testSpec.assertWm {
+ flicker.assertWm {
this.notContains(testApp)
.then()
.isAppWindowInvisible(testApp, isOptional = true)
@@ -97,7 +97,7 @@
}
protected fun appWindowBecomesVisible_warmStart() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowInvisible(testApp)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -115,7 +115,7 @@
@Presubmit
@Test
open fun appWindowBecomesTopWindow() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowNotOnTop(testApp)
.then()
.isAppWindowOnTop(
@@ -131,6 +131,6 @@
@Presubmit
@Test
open fun appWindowIsTopWindowAtEnd() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 03741c8..62d7cc0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -19,12 +19,12 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.annotation.FlickerServiceCompatible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -57,8 +57,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
- OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppWarmTest(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** Defines the transition used to run the test */
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -68,7 +67,7 @@
testApp.launchViaIntent(wmHelper)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- this.setRotation(testSpec.startRotation)
+ this.setRotation(flicker.scenario.startRotation)
}
teardown { testApp.exit(wmHelper) }
transitions { testApp.launchViaIntent(wmHelper) }
@@ -96,13 +95,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index e1fd5a7..b9594a1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -22,16 +22,16 @@
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.R
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.junit.FlickerBuilderProvider
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
@@ -55,7 +55,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OverrideTaskTransitionTest(val testSpec: FlickerTestParameter) {
+class OverrideTaskTransitionTest(val flicker: FlickerTest) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
@@ -66,7 +66,7 @@
setup {
device.wakeUpAndGoToHomeScreen()
RemoveAllTasksButHomeRule.removeAllTasksButHome()
- setRotation(testSpec.startRotation)
+ setRotation(flicker.scenario.startRotation)
}
transitions {
instrumentation.context.startActivity(
@@ -87,24 +87,24 @@
@Presubmit
@Test
fun testSimpleActivityIsShownDirectly() {
- testSpec.assertLayers {
+ flicker.assertLayers {
// Before the app launches, only the launcher is visible.
isVisible(ComponentNameMatcher.LAUNCHER)
- .isInvisible(testApp)
- .then()
- // Animation starts, but the app may not be drawn yet which means the Splash
- // may be visible.
- .isInvisible(testApp, isOptional = true)
- .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
- .then()
- // App shows up with the custom animation starting at alpha=1.
- .isVisible(testApp)
- .then()
- // App custom animation continues to alpha=0 (invisible).
- .isInvisible(testApp)
- .then()
- // App custom animation ends with it being visible.
- .isVisible(testApp)
+ .isInvisible(testApp)
+ .then()
+ // Animation starts, but the app may not be drawn yet which means the Splash
+ // may be visible.
+ .isInvisible(testApp, isOptional = true)
+ .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+ .then()
+ // App shows up with the custom animation starting at alpha=1.
+ .isVisible(testApp)
+ .then()
+ // App custom animation continues to alpha=0 (invisible).
+ .isInvisible(testApp)
+ .then()
+ // App custom animation ends with it being visible.
+ .isVisible(testApp)
}
}
@@ -123,8 +123,8 @@
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 08624ee..4e7ab7a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -22,13 +22,13 @@
import android.platform.test.annotations.Postsubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NewTasksAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.SPLASH_SCREEN
import com.android.server.wm.traces.common.ComponentNameMatcher.Companion.WALLPAPER_BBQ_WRAPPER
@@ -56,7 +56,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class TaskTransitionTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class TaskTransitionTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = NewTasksAppHelper(instrumentation)
private val simpleApp = SimpleAppHelper(instrumentation)
private val wallpaper by lazy {
@@ -81,7 +81,7 @@
@FlakyTest(bugId = 253617416)
@Test
fun wallpaperWindowIsNeverVisible() {
- testSpec.assertWm { this.isNonAppWindowInvisible(wallpaper) }
+ flicker.assertWm { this.isNonAppWindowInvisible(wallpaper) }
}
/**
@@ -91,7 +91,7 @@
@FlakyTest(bugId = 253617416)
@Test
fun wallpaperLayerIsNeverVisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isInvisible(wallpaper)
this.isInvisible(WALLPAPER_BBQ_WRAPPER)
}
@@ -104,7 +104,7 @@
@Postsubmit
@Test
fun launcherWindowIsNeverVisible() {
- testSpec.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertWm { this.isAppWindowInvisible(ComponentNameMatcher.LAUNCHER) }
}
/**
@@ -114,7 +114,7 @@
@Postsubmit
@Test
fun launcherLayerIsNeverVisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.LAUNCHER) }
}
/** Checks that a color background is visible while the task transition is occurring. */
@@ -122,9 +122,9 @@
@Test
fun colorLayerIsVisibleDuringTransition() {
val bgColorLayer = ComponentNameMatcher("", "colorBackgroundLayer")
- val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
+ val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") {
it.visibleRegion(testApp.componentMatcher).coversExactly(displayBounds)
}
@@ -157,7 +157,7 @@
@Postsubmit
@Test
fun newTaskOpensOnTopAndThenCloses() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(testApp.componentMatcher)
.then()
.isAppWindowOnTop(SPLASH_SCREEN, isOptional = true)
@@ -235,8 +235,8 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index bc1f0d1..b4a67ef 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -18,18 +18,17 @@
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -54,7 +53,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -66,7 +65,7 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
tapl.setIgnoreTaskbarVisibility(true)
testApp1.launchViaIntent(wmHelper)
testApp2.launchViaIntent(wmHelper)
@@ -96,7 +95,7 @@
@Presubmit
@Test
open fun startsWithApp2WindowsCoverFullScreen() {
- testSpec.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
+ flicker.assertWmStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
}
/**
@@ -106,16 +105,14 @@
@Presubmit
@Test
open fun startsWithApp2LayersCoverFullScreen() {
- testSpec.assertLayersStart {
- this.visibleRegion(testApp2).coversExactly(startDisplayBounds)
- }
+ flicker.assertLayersStart { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
}
/** Checks that the transition starts with [testApp2] being the top window. */
@Presubmit
@Test
open fun startsWithApp2WindowBeingOnTop() {
- testSpec.assertWmStart { this.isAppWindowOnTop(testApp2) }
+ flicker.assertWmStart { this.isAppWindowOnTop(testApp2) }
}
/**
@@ -125,7 +122,7 @@
@Presubmit
@Test
open fun endsWithApp1WindowsCoveringFullScreen() {
- testSpec.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
+ flicker.assertWmEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
}
/**
@@ -135,7 +132,7 @@
@Presubmit
@Test
fun endsWithApp1LayersCoveringFullScreen() {
- testSpec.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
}
/**
@@ -145,7 +142,7 @@
@Presubmit
@Test
open fun endsWithApp1BeingOnTop() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp1) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp1) }
}
/**
@@ -155,7 +152,7 @@
@Presubmit
@Test
open fun app1WindowBecomesAndStaysVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowInvisible(testApp1)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -171,7 +168,7 @@
@Presubmit
@Test
open fun app1LayerBecomesAndStaysVisible() {
- testSpec.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
+ flicker.assertLayers { this.isInvisible(testApp1).then().isVisible(testApp1) }
}
/**
@@ -181,9 +178,7 @@
@Presubmit
@Test
open fun app2WindowBecomesAndStaysInvisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2)
- }
+ flicker.assertWm { this.isAppWindowVisible(testApp2).then().isAppWindowInvisible(testApp2) }
}
/**
@@ -193,7 +188,7 @@
@Presubmit
@Test
open fun app2LayerBecomesAndStaysInvisible() {
- testSpec.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
+ flicker.assertLayers { this.isVisible(testApp2).then().isInvisible(testApp2) }
}
/**
@@ -204,7 +199,7 @@
@Presubmit
@Test
open fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowVisible(testApp2)
.then()
// TODO: Do we actually want to test this? Seems too implementation specific...
@@ -224,7 +219,7 @@
@Presubmit
@Test
open fun app1LayerIsVisibleOnceApp2LayerIsInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(testApp2)
.then()
.isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -240,13 +235,10 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
index f988bb2..ec4e35c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest_ShellTransit.kt
@@ -19,9 +19,9 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
@@ -49,8 +49,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(testSpec: FlickerTestParameter) :
- QuickSwitchBetweenTwoAppsBackTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsBackTest_ShellTransit(flicker: FlickerTest) :
+ QuickSwitchBetweenTwoAppsBackTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
@@ -62,21 +62,20 @@
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
- * the start and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+ * start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
/** {@inheritDoc} */
@FlakyTest(bugId = 250520840)
@Test
- override fun startsWithApp2LayersCoverFullScreen() =
- super.startsWithApp2LayersCoverFullScreen()
+ override fun startsWithApp2LayersCoverFullScreen() = super.startsWithApp2LayersCoverFullScreen()
@FlakyTest(bugId = 246284708)
@Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 7e4504b..593481c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -18,18 +18,17 @@
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -55,8 +54,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest(testSpec: FlickerTestParameter) :
- BaseTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -68,7 +66,7 @@
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
setup {
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp1.launchViaIntent(wmHelper)
testApp2.launchViaIntent(wmHelper)
@@ -105,7 +103,7 @@
@Presubmit
@Test
open fun startsWithApp1WindowsCoverFullScreen() {
- testSpec.assertWmStart {
+ flicker.assertWmStart {
this.visibleRegion(testApp1.or(ComponentNameMatcher.LETTERBOX))
.coversExactly(startDisplayBounds)
}
@@ -118,16 +116,14 @@
@Presubmit
@Test
open fun startsWithApp1LayersCoverFullScreen() {
- testSpec.assertLayersStart {
- this.visibleRegion(testApp1).coversExactly(startDisplayBounds)
- }
+ flicker.assertLayersStart { this.visibleRegion(testApp1).coversExactly(startDisplayBounds) }
}
/** Checks that the transition starts with [testApp1] being the top window. */
@Presubmit
@Test
open fun startsWithApp1WindowBeingOnTop() {
- testSpec.assertWmStart { this.isAppWindowOnTop(testApp1) }
+ flicker.assertWmStart { this.isAppWindowOnTop(testApp1) }
}
/**
@@ -137,7 +133,7 @@
@Presubmit
@Test
open fun endsWithApp2WindowsCoveringFullScreen() {
- testSpec.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
+ flicker.assertWmEnd { this.visibleRegion(testApp2).coversExactly(startDisplayBounds) }
}
/**
@@ -147,7 +143,7 @@
@Presubmit
@Test
open fun endsWithApp2LayersCoveringFullScreen() {
- testSpec.assertLayersEnd {
+ flicker.assertLayersEnd {
this.visibleRegion(testApp2.or(ComponentNameMatcher.LETTERBOX))
.coversExactly(startDisplayBounds)
}
@@ -160,7 +156,7 @@
@Presubmit
@Test
open fun endsWithApp2BeingOnTop() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp2) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp2) }
}
/**
@@ -170,7 +166,7 @@
@Presubmit
@Test
open fun app2WindowBecomesAndStaysVisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowInvisible(testApp2)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -186,7 +182,7 @@
@Presubmit
@Test
open fun app2LayerBecomesAndStaysVisible() {
- testSpec.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
+ flicker.assertLayers { this.isInvisible(testApp2).then().isVisible(testApp2) }
}
/**
@@ -196,9 +192,7 @@
@Presubmit
@Test
open fun app1WindowBecomesAndStaysInvisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1)
- }
+ flicker.assertWm { this.isAppWindowVisible(testApp1).then().isAppWindowInvisible(testApp1) }
}
/**
@@ -208,7 +202,7 @@
@Presubmit
@Test
open fun app1LayerBecomesAndStaysInvisible() {
- testSpec.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
+ flicker.assertLayers { this.isVisible(testApp1).then().isInvisible(testApp1) }
}
/**
@@ -219,7 +213,7 @@
@Presubmit
@Test
open fun app2WindowIsVisibleOnceApp1WindowIsInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowVisible(testApp1)
.then()
.isAppWindowVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -238,7 +232,7 @@
@Presubmit
@Test
open fun app2LayerIsVisibleOnceApp1LayerIsInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(testApp1)
.then()
.isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
@@ -259,13 +253,10 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
index cc954ab..477b419 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest_ShellTransit.kt
@@ -19,9 +19,9 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Assume
@@ -50,8 +50,8 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(testSpec: FlickerTestParameter) :
- QuickSwitchBetweenTwoAppsForwardTest(testSpec) {
+open class QuickSwitchBetweenTwoAppsForwardTest_ShellTransit(flicker: FlickerTest) :
+ QuickSwitchBetweenTwoAppsForwardTest(flicker) {
@Before
override fun before() {
Assume.assumeTrue(isShellTransitionsEnabled)
@@ -63,14 +63,14 @@
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at
- * the start and end of the WM trace
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
+ * start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
@FlakyTest(bugId = 246284708)
@@ -84,6 +84,5 @@
@FlakyTest(bugId = 250522691)
@Test
- override fun startsWithApp1LayersCoverFullScreen() =
- super.startsWithApp1LayersCoverFullScreen()
+ override fun startsWithApp1LayersCoverFullScreen() = super.startsWithApp1LayersCoverFullScreen()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index 3cb985a..8c8220f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -19,18 +19,17 @@
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.navBarWindowIsVisibleAtStartAndEnd
import com.android.server.wm.traces.common.ComponentNameMatcher
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.service.PlatformConsts
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
@@ -55,7 +54,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class QuickSwitchFromLauncherTest(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+class QuickSwitchFromLauncherTest(flicker: FlickerTest) : BaseTest(flicker) {
private val testApp = SimpleAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -63,7 +62,7 @@
setup {
tapl.setExpectedRotationCheckEnabled(false)
- tapl.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(flicker.scenario.startRotation.value)
testApp.launchViaIntent(wmHelper)
tapl.goHome()
@@ -95,7 +94,7 @@
@Presubmit
@Test
fun endsWithAppWindowsCoveringFullScreen() {
- testSpec.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
+ flicker.assertWmEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
}
/**
@@ -105,7 +104,7 @@
@Presubmit
@Test
fun endsWithAppLayersCoveringFullScreen() {
- testSpec.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
+ flicker.assertLayersEnd { this.visibleRegion(testApp).coversExactly(startDisplayBounds) }
}
/**
@@ -115,47 +114,48 @@
@Presubmit
@Test
fun endsWithAppBeingOnTop() {
- testSpec.assertWmEnd { this.isAppWindowOnTop(testApp) }
+ flicker.assertWmEnd { this.isAppWindowOnTop(testApp) }
}
/** Checks that the transition starts with the home activity being tagged as visible. */
@Presubmit
@Test
fun startsWithHomeActivityFlaggedVisible() {
- testSpec.assertWmStart { this.isHomeActivityVisible() }
+ flicker.assertWmStart { this.isHomeActivityVisible() }
}
/**
- * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] windows
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows
* filling/covering exactly display size
*/
@Presubmit
@Test
fun startsWithLauncherWindowsCoverFullScreen() {
- testSpec.assertWmStart {
+ flicker.assertWmStart {
this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
}
}
/**
- * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] layers
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers
* filling/covering exactly the display size.
*/
@Presubmit
@Test
fun startsWithLauncherLayersCoverFullScreen() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds)
}
}
/**
- * Checks that the transition starts with the [ComponentMatcher.LAUNCHER] being the top window.
+ * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top
+ * window.
*/
@Presubmit
@Test
fun startsWithLauncherBeingOnTop() {
- testSpec.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
+ flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) }
}
/**
@@ -165,7 +165,7 @@
@Presubmit
@Test
fun endsWithHomeActivityFlaggedInvisible() {
- testSpec.assertWmEnd { this.isHomeActivityInvisible() }
+ flicker.assertWmEnd { this.isHomeActivityInvisible() }
}
/**
@@ -175,7 +175,7 @@
@Presubmit
@Test
fun appWindowBecomesAndStaysVisible() {
- testSpec.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
+ flicker.assertWm { this.isAppWindowInvisible(testApp).then().isAppWindowVisible(testApp) }
}
/**
@@ -185,18 +185,18 @@
@Presubmit
@Test
fun appLayerBecomesAndStaysVisible() {
- testSpec.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
+ flicker.assertLayers { this.isInvisible(testApp).then().isVisible(testApp) }
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] window starts off visible and becomes invisible
- * at some point before the end of the transition and then stays invisible until the end of the
- * transition.
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
*/
@Presubmit
@Test
fun launcherWindowBecomesAndStaysInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER)
@@ -204,14 +204,14 @@
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] layer starts off visible and becomes invisible at
- * some point before the end of the transition and then stays invisible until the end of the
- * transition.
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes
+ * invisible at some point before the end of the transition and then stays invisible until the
+ * end of the transition.
*/
@Presubmit
@Test
fun launcherLayerBecomesAndStaysInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isInvisible(ComponentNameMatcher.LAUNCHER)
@@ -219,14 +219,14 @@
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] window is visible at least until the app window
- * is visible. Ensures that at any point, either the launcher or [testApp] windows are at least
- * partially visible.
+ * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app
+ * window is visible. Ensures that at any point, either the launcher or [testApp] windows are at
+ * least partially visible.
*/
@Presubmit
@Test
fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
- testSpec.assertWm {
+ flicker.assertWm {
this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
.then()
.isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -236,14 +236,14 @@
}
/**
- * Checks that the [ComponentMatcher.LAUNCHER] layer is visible at least until the app layer is
- * visible. Ensures that at any point, either the launcher or [testApp] layers are at least
+ * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer
+ * is visible. Ensures that at any point, either the launcher or [testApp] layers are at least
* partially visible.
*/
@Presubmit
@Test
fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(ComponentNameMatcher.LAUNCHER)
.then()
.isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
@@ -263,14 +263,14 @@
override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
/**
- * Checks that [ComponentMatcher.NAV_BAR] window is visible and above the app windows at the
+ * Checks that [ComponentNameMatcher.NAV_BAR] window is visible and above the app windows at the
* start and end of the WM trace
*/
@Presubmit
@Test
fun navBarWindowIsVisibleAtStartAndEnd() {
- Assume.assumeFalse(testSpec.isTablet)
- testSpec.navBarWindowIsVisibleAtStartAndEnd()
+ Assume.assumeFalse(flicker.scenario.isTablet)
+ flicker.navBarWindowIsVisibleAtStartAndEnd()
}
@Presubmit
@@ -293,14 +293,12 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(
- supportedNavigationModes =
- listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY),
- // TODO: Test with 90 rotation
- supportedRotations = listOf(Surface.ROTATION_0)
- )
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(PlatformConsts.NavBar.MODE_GESTURAL),
+ // TODO: Test with 90 rotation
+ supportedRotations = listOf(PlatformConsts.Rotation.ROTATION_0)
+ )
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index ad14d0d..5b52c75 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -20,11 +20,11 @@
import android.platform.test.annotations.IwTest
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
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
import org.junit.Test
@@ -78,7 +78,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class ChangeAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
+class ChangeAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
override val testApp = SimpleAppHelper(instrumentation)
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -93,15 +93,15 @@
@Presubmit
@Test
fun focusChanges() {
- testSpec.assertEventLog { this.focusChanges(testApp.`package`) }
+ flicker.assertEventLog { this.focusChanges(testApp.`package`) }
}
/**
- * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
* flicker, and disappears before the transition is complete
*/
fun rotationLayerAppearsAndVanishesAssertion() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.isVisible(testApp)
.then()
.isVisible(ComponentNameMatcher.ROTATION)
@@ -112,7 +112,7 @@
}
/**
- * Checks that the [ComponentMatcher.ROTATION] layer appears during the transition, doesn't
+ * Checks that the [ComponentNameMatcher.ROTATION] layer appears during the transition, doesn't
* flicker, and disappears before the transition is complete
*/
@Presubmit
@@ -138,13 +138,13 @@
/**
* Creates the test configurations.
*
- * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
+ * See [FlickerTestFactory.rotationTests] for configuring screen orientation and navigation
+ * modes.
*/
@Parameterized.Parameters(name = "{0}")
@JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigRotationTests()
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 8e3fd40..4ef9eaf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -18,29 +18,29 @@
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.BaseTest
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.Test
/** Base class for app rotation tests */
-abstract class RotationTransition(testSpec: FlickerTestParameter) : BaseTest(testSpec) {
+abstract class RotationTransition(flicker: FlickerTest) : BaseTest(flicker) {
protected abstract val testApp: StandardAppHelper
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit = {
- setup { this.setRotation(testSpec.startRotation) }
+ setup { this.setRotation(flicker.scenario.startRotation) }
teardown { testApp.exit(wmHelper) }
- transitions { this.setRotation(testSpec.endRotation) }
+ transitions { this.setRotation(flicker.scenario.endRotation) }
}
/** {@inheritDoc} */
@Presubmit
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.visibleLayersShownMoreThanOneConsecutiveEntry(
ignoreLayers =
listOf(
@@ -56,7 +56,7 @@
@Presubmit
@Test
open fun appLayerRotates_StartingPos() {
- testSpec.assertLayersStart {
+ flicker.assertLayersStart {
this.entry.displays.map { display ->
this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
}
@@ -67,7 +67,7 @@
@Presubmit
@Test
open fun appLayerRotates_EndingPos() {
- testSpec.assertLayersEnd {
+ flicker.assertLayersEnd {
this.entry.displays.map { display ->
this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index d0d4122..54f38c3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -21,11 +21,12 @@
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.WindowManager
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-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.flicker.FlickerBuilder
+import com.android.server.wm.flicker.FlickerTest
+import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.ScenarioBuilder
import com.android.server.wm.flicker.helpers.SeamlessRotationAppHelper
+import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.ComponentNameMatcher
import org.junit.FixMethodOrder
@@ -38,7 +39,7 @@
/**
* Test opening an app and cycling through app rotations using seamless rotations
*
- * Currently runs:
+ * Currently, runs:
* ```
* 0 -> 90 degrees
* 0 -> 90 degrees (with starved UI thread)
@@ -83,7 +84,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-open class SeamlessAppRotationTest(testSpec: FlickerTestParameter) : RotationTransition(testSpec) {
+open class SeamlessAppRotationTest(flicker: FlickerTest) : RotationTransition(flicker) {
override val testApp = SeamlessRotationAppHelper(instrumentation)
/** {@inheritDoc} */
@@ -96,7 +97,7 @@
stringExtras =
mapOf(
ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD to
- testSpec.starveUiThread.toString()
+ flicker.starveUiThread.toString()
)
)
}
@@ -106,7 +107,7 @@
@Presubmit
@Test
fun appWindowFullScreen() {
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("isFullScreen") {
val appWindow = it.windowState(testApp.`package`)
val flags = appWindow.windowState?.attributes?.flags ?: 0
@@ -122,7 +123,7 @@
@Presubmit
@Test
fun appWindowSeamlessRotation() {
- testSpec.assertWm {
+ flicker.assertWm {
this.invoke("isRotationSeamless") {
val appWindow = it.windowState(testApp.`package`)
val rotationAnimation = appWindow.windowState?.attributes?.rotationAnimation ?: 0
@@ -142,14 +143,14 @@
@Presubmit
@Test
fun appLayerAlwaysVisible() {
- testSpec.assertLayers { isVisible(testApp) }
+ flicker.assertLayers { isVisible(testApp) }
}
/** Checks that [testApp] layer covers the entire screen during the whole transition */
@Presubmit
@Test
fun appLayerRotates() {
- testSpec.assertLayers {
+ flicker.assertLayers {
this.invoke("entireScreenCovered") { entry ->
entry.entry.displays.map { display ->
entry.visibleRegion(testApp).coversExactly(display.layerStackSpace)
@@ -180,7 +181,7 @@
@Presubmit
@Test
fun statusBarWindowIsAlwaysInvisible() {
- testSpec.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertWm { this.isAboveAppWindowInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/**
@@ -190,14 +191,14 @@
@Presubmit
@Test
fun statusBarLayerIsAlwaysInvisible() {
- testSpec.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
+ flicker.assertLayers { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
}
/** Checks that the focus doesn't change during animation */
@Presubmit
@Test
fun focusDoesNotChange() {
- testSpec.assertEventLog { this.focusDoesNotChange() }
+ flicker.assertEventLog { this.focusDoesNotChange() }
}
/** {@inheritDoc} */
@@ -208,7 +209,7 @@
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
- if (!testSpec.isTablet) {
+ if (!flicker.scenario.isTablet) {
// not yet tablet compatible
appLayerRotates()
appLayerAlwaysVisible()
@@ -231,49 +232,39 @@
}
companion object {
- private val FlickerTestParameter.starveUiThread
+ private val FlickerTest.starveUiThread
get() =
- config.getOrDefault(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD, false)
- as Boolean
+ getConfigValue<Boolean>(ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD)
+ ?: false
- private fun createConfig(
- sourceConfig: FlickerTestParameter,
- starveUiThread: Boolean
- ): FlickerTestParameter {
- val newConfig =
- sourceConfig.config.toMutableMap().also {
- it[ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD] = starveUiThread
- }
+ private fun createConfig(sourceConfig: FlickerTest, starveUiThread: Boolean): FlickerTest {
+ val originalScenario = sourceConfig.initialize("createConfig")
val nameExt = if (starveUiThread) "_BUSY_UI_THREAD" else ""
- return FlickerTestParameter(newConfig, nameOverride = "$sourceConfig$nameExt")
+ val newConfig =
+ ScenarioBuilder()
+ .fromScenario(originalScenario)
+ .withExtraConfig(
+ ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD,
+ starveUiThread
+ )
+ .withDescriptionOverride("${originalScenario.description}$nameExt")
+ return FlickerTest(newConfig)
}
/**
* Creates the test configurations for seamless rotation based on the default rotation tests
- * from [FlickerTestParameterFactory.getConfigRotationTests], but adding an additional flag
- * ([ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
+ * from [FlickerTestFactory.rotationTests], but adding a flag (
+ * [ActivityOptions.SeamlessRotation.EXTRA_STARVE_UI_THREAD]) to indicate if the app should
* starve the UI thread of not
- */
+ */
+ @Parameterized.Parameters(name = "{0}")
@JvmStatic
- private fun getConfigurations(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigRotationTests().flatMap {
- sourceConfig ->
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.rotationTests().flatMap { sourceConfig ->
val defaultRun = createConfig(sourceConfig, starveUiThread = false)
val busyUiRun = createConfig(sourceConfig, starveUiThread = true)
listOf(defaultRun, busyUiRun)
}
}
-
- /**
- * Creates the test configurations.
- *
- * See [FlickerTestParameterFactory.getConfigRotationTests] for configuring repetitions,
- * screen orientation and navigation modes.
- */
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return getConfigurations()
- }
}
}
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index e6b60cf..167d560 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -88,46 +88,7 @@
}
private static String insetsTypesToString(int types) {
- if (types == 0) {
- return "none";
- }
- final StringBuilder sb = new StringBuilder();
- if ((types & Type.statusBars()) != 0) {
- types &= ~Type.statusBars();
- sb.append("statusBars ");
- }
- if ((types & Type.navigationBars()) != 0) {
- types &= ~Type.navigationBars();
- sb.append("navigationBars ");
- }
- if ((types & Type.captionBar()) != 0) {
- types &= ~Type.captionBar();
- sb.append("captionBar ");
- }
- if ((types & Type.ime()) != 0) {
- types &= ~Type.ime();
- sb.append("ime ");
- }
- if ((types & Type.systemGestures()) != 0) {
- types &= ~Type.systemGestures();
- sb.append("systemGestures ");
- }
- if ((types & Type.mandatorySystemGestures()) != 0) {
- types &= ~Type.mandatorySystemGestures();
- sb.append("mandatorySystemGestures ");
- }
- if ((types & Type.tappableElement()) != 0) {
- types &= ~Type.tappableElement();
- sb.append("tappableElement ");
- }
- if ((types & Type.displayCutout()) != 0) {
- types &= ~Type.displayCutout();
- sb.append("displayCutout ");
- }
- if (types != 0) {
- sb.append("unknownTypes:").append(types);
- }
- return sb.toString();
+ return types == 0 ? "none" : WindowInsets.Type.toString(types);
}
@Override
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index d85a5bd..5da18dc 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -393,6 +393,21 @@
mEventHandler = new Handler(context.getMainLooper());
}
+ /**
+ * Construct WifiNl80211Manager with giving context and binder which is an interface of
+ * IWificond.
+ *
+ * @param context Android context.
+ * @param binder a binder of IWificond.
+ */
+ public WifiNl80211Manager(@NonNull Context context, @NonNull IBinder binder) {
+ this(context);
+ mWificond = IWificond.Stub.asInterface(binder);
+ if (mWificond == null) {
+ Log.e(TAG, "Failed to get reference to wificond");
+ }
+ }
+
/** @hide */
@VisibleForTesting
public WifiNl80211Manager(Context context, IWificond wificond) {