diff options
878 files changed, 23490 insertions, 8113 deletions
diff --git a/Android.bp b/Android.bp index 0315c12f95a1..0a1456514010 100644 --- a/Android.bp +++ b/Android.bp @@ -214,6 +214,7 @@ java_library { "android.hardware.radio-V1.5-java", "android.hardware.radio-V1.6-java", "android.hardware.radio.data-V1-java", + "android.hardware.radio.ims-V1-java", "android.hardware.radio.messaging-V1-java", "android.hardware.radio.modem-V1-java", "android.hardware.radio.network-V2-java", diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 5a445d476711..dade7c3d84a8 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -309,7 +309,7 @@ public class AlarmManager { /** * Callback method that is invoked by the system when the alarm time is reached. */ - public void onAlarm(); + void onAlarm(); } final class ListenerWrapper extends IAlarmListener.Stub implements Runnable { @@ -453,7 +453,7 @@ public class AlarmManager { * @see #RTC * @see #RTC_WAKEUP */ - public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { + public void set(@AlarmType int type, long triggerAtMillis, @NonNull PendingIntent operation) { setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null, (Handler) null, null, null); } @@ -480,8 +480,8 @@ public class AlarmManager { * @param targetHandler {@link Handler} on which to execute the listener's onAlarm() * callback, or {@code null} to run that callback on the main looper. */ - public void set(@AlarmType int type, long triggerAtMillis, String tag, OnAlarmListener listener, - Handler targetHandler) { + public void set(@AlarmType int type, long triggerAtMillis, @Nullable String tag, + @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) { setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag, targetHandler, null, null); } @@ -546,7 +546,7 @@ public class AlarmManager { * @see Intent#EXTRA_ALARM_COUNT */ public void setRepeating(@AlarmType int type, long triggerAtMillis, - long intervalMillis, PendingIntent operation) { + long intervalMillis, @NonNull PendingIntent operation) { setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null, null, (Handler) null, null, null); } @@ -602,7 +602,7 @@ public class AlarmManager { * @see #RTC_WAKEUP */ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, - PendingIntent operation) { + @NonNull PendingIntent operation) { setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null, (Handler) null, null, null); } @@ -625,12 +625,62 @@ public class AlarmManager { * @see #setWindow(int, long, long, PendingIntent) */ public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, - String tag, OnAlarmListener listener, Handler targetHandler) { + @Nullable String tag, @NonNull OnAlarmListener listener, + @Nullable Handler targetHandler) { setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag, targetHandler, null, null); } /** + * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather + * than supplying a PendingIntent to be sent when the alarm time is reached, this variant + * supplies an {@link OnAlarmListener} instance that will be invoked at that time. + * <p> + * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be + * invoked via the specified target Executor. + * + * <p> + * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of + * less than 10 minutes. The system will try its best to accommodate smaller windows if the + * alarm is supposed to fire in the near future, but there are no guarantees and the app should + * expect any window smaller than 10 minutes to get elongated to 10 minutes. + * + * @see #setWindow(int, long, long, PendingIntent) + */ + public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, + @Nullable String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) { + setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag, + executor, null, null); + } + + /** + * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather + * than supplying a PendingIntent to be sent when the alarm time is reached, this variant + * supplies an {@link OnAlarmListener} instance that will be invoked at that time. + * <p> + * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be + * invoked via the specified target Executor. + * + * <p> + * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of + * less than 10 minutes. The system will try its best to accommodate smaller windows if the + * alarm is supposed to fire in the near future, but there are no guarantees and the app should + * expect any window smaller than 10 minutes to get elongated to 10 minutes. + * + * @see #setWindow(int, long, long, PendingIntent) + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) + public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, + @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource, + @NonNull OnAlarmListener listener) { + setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag, + executor, workSource, null); + } + + /** * Schedule an alarm that is prioritized by the system while the device is in power saving modes * such as battery saver and device idle (doze). * @@ -725,7 +775,8 @@ public class AlarmManager { * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM */ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true) - public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) { + public void setExact(@AlarmType int type, long triggerAtMillis, + @NonNull PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null, null, null); } @@ -756,8 +807,8 @@ public class AlarmManager { * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM */ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true) - public void setExact(@AlarmType int type, long triggerAtMillis, String tag, - OnAlarmListener listener, Handler targetHandler) { + public void setExact(@AlarmType int type, long triggerAtMillis, @Nullable String tag, + @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag, targetHandler, null, null); } @@ -767,8 +818,8 @@ public class AlarmManager { * the given time. * @hide */ - public void setIdleUntil(@AlarmType int type, long triggerAtMillis, String tag, - OnAlarmListener listener, Handler targetHandler) { + public void setIdleUntil(@AlarmType int type, long triggerAtMillis, @Nullable String tag, + @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null, listener, tag, targetHandler, null, null); } @@ -828,7 +879,7 @@ public class AlarmManager { * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM */ @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM) - public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) { + public void setAlarmClock(@NonNull AlarmClockInfo info, @NonNull PendingIntent operation) { setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null, null, info); } @@ -837,7 +888,8 @@ public class AlarmManager { @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, - long intervalMillis, PendingIntent operation, WorkSource workSource) { + long intervalMillis, @NonNull PendingIntent operation, + @Nullable WorkSource workSource) { setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null, (Handler) null, workSource, null); } @@ -854,8 +906,8 @@ public class AlarmManager { */ @UnsupportedAppUsage public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, - long intervalMillis, String tag, OnAlarmListener listener, Handler targetHandler, - WorkSource workSource) { + long intervalMillis, @Nullable String tag, @NonNull OnAlarmListener listener, + @Nullable Handler targetHandler, @Nullable WorkSource workSource) { setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag, targetHandler, workSource, null); } @@ -873,8 +925,8 @@ public class AlarmManager { @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(@AlarmType int type, long triggerAtMillis, long windowMillis, - long intervalMillis, OnAlarmListener listener, Handler targetHandler, - WorkSource workSource) { + long intervalMillis, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler, + @Nullable WorkSource workSource) { setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null, targetHandler, workSource, null); } @@ -1072,7 +1124,7 @@ public class AlarmManager { * @see Intent#EXTRA_ALARM_COUNT */ public void setInexactRepeating(@AlarmType int type, long triggerAtMillis, - long intervalMillis, PendingIntent operation) { + long intervalMillis, @NonNull PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null, (Handler) null, null, null); } @@ -1122,7 +1174,7 @@ public class AlarmManager { * @see #RTC_WAKEUP */ public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, - PendingIntent operation) { + @NonNull PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation, null, null, (Handler) null, null, null); } @@ -1195,12 +1247,46 @@ public class AlarmManager { */ @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true) public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, - PendingIntent operation) { + @NonNull PendingIntent operation) { setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation, null, null, (Handler) null, null, null); } /** + * Like {@link #setExact(int, long, String, Executor, WorkSource, OnAlarmListener)}, but this + * alarm will be allowed to execute even when the system is in low-power idle modes. + * + * <p> See {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} for more details. + * + * @param type type of alarm + * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered, + * expressed in the appropriate clock's units (depending on the alarm + * type). + * @param listener {@link OnAlarmListener} instance whose + * {@link OnAlarmListener#onAlarm() onAlarm()} method will be called when + * the alarm time is reached. + * @param executor The {@link Executor} on which to execute the listener's onAlarm() + * callback. + * @param tag Optional. A string tag used to identify this alarm in logs and + * battery-attribution. + * @param workSource A {@link WorkSource} object to attribute this alarm to the app that + * requested this work. + * @hide + */ + @SystemApi + @RequiresPermission(allOf = { + Manifest.permission.UPDATE_DEVICE_STATS, + Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional = true) + public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, + @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource, + @NonNull OnAlarmListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, null, listener, tag, + executor, workSource, null); + } + + /** * Remove any alarms with a matching {@link Intent}. * Any alarm, of any type, whose Intent matches this one (as defined by * {@link Intent#filterEquals}), will be canceled. @@ -1210,7 +1296,7 @@ public class AlarmManager { * * @see #set */ - public void cancel(PendingIntent operation) { + public void cancel(@NonNull PendingIntent operation) { if (operation == null) { final String msg = "cancel() called with a null PendingIntent"; if (mTargetSdkVersion >= Build.VERSION_CODES.N) { @@ -1233,7 +1319,7 @@ public class AlarmManager { * * @param listener OnAlarmListener instance that is the target of a currently-set alarm. */ - public void cancel(OnAlarmListener listener) { + public void cancel(@NonNull OnAlarmListener listener) { if (listener == null) { throw new NullPointerException("cancel() called with a null OnAlarmListener"); } diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index 299ad66a882c..4a3a6d912966 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -113,7 +113,9 @@ public class EconomyManager { /** @hide */ public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit"; /** @hide */ - public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit"; + public static final String KEY_AM_MIN_CONSUMPTION_LIMIT = "am_minimum_consumption_limit"; + /** @hide */ + public static final String KEY_AM_MAX_CONSUMPTION_LIMIT = "am_maximum_consumption_limit"; // TODO: Add AlarmManager modifier keys /** @hide */ public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT = @@ -242,7 +244,9 @@ public class EconomyManager { /** @hide */ public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit"; /** @hide */ - public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit"; + public static final String KEY_JS_MIN_CONSUMPTION_LIMIT = "js_minimum_consumption_limit"; + /** @hide */ + public static final String KEY_JS_MAX_CONSUMPTION_LIMIT = "js_maximum_consumption_limit"; // TODO: Add JobScheduler modifier keys /** @hide */ public static final String KEY_JS_REWARD_APP_INSTALL_INSTANT = @@ -371,7 +375,9 @@ public class EconomyManager { /** @hide */ public static final long DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(2880); /** @hide */ - public static final long DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000); + public static final long DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES = arcToCake(1440); + /** @hide */ + public static final long DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000); // TODO: add AlarmManager modifier default values /** @hide */ public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0); @@ -478,8 +484,10 @@ public class EconomyManager { /** @hide */ public static final long DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(29_000); /** @hide */ - // TODO: set hard limit based on device type (phone vs tablet vs etc) + battery size - public static final long DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000); + public static final long DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES = arcToCake(17_000); + /** @hide */ + // TODO: set maximum limit based on device type (phone vs tablet vs etc) + battery size + public static final long DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000); // TODO: add JobScheduler modifier default values /** @hide */ public static final long DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES = arcToCake(408); 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 6375d0deae40..d9fb318c9335 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -432,6 +432,7 @@ public class JobSchedulerService extends com.android.server.SystemService break; case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS: case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS: + case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO: mConstants.updateBackoffConstantsLocked(); break; case Constants.KEY_CONN_CONGESTION_DELAY_FRAC: @@ -509,6 +510,8 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String KEY_MIN_LINEAR_BACKOFF_TIME_MS = "min_linear_backoff_time_ms"; private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms"; + private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO = + "system_stop_to_failure_ratio"; private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac"; private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac"; private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH = @@ -540,6 +543,7 @@ public class JobSchedulerService extends com.android.server.SystemService private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS; private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS; + private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f; private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true; @@ -589,6 +593,11 @@ public class JobSchedulerService extends com.android.server.SystemService * The minimum backoff time to allow for exponential backoff. */ long MIN_EXP_BACKOFF_TIME_MS = DEFAULT_MIN_EXP_BACKOFF_TIME_MS; + /** + * The ratio to use to convert number of times a job was stopped by JobScheduler to an + * incremental failure in the backoff policy calculation. + */ + int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO; /** * The fraction of a job's running window that must pass before we @@ -700,6 +709,9 @@ public class JobSchedulerService extends com.android.server.SystemService MIN_EXP_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EXP_BACKOFF_TIME_MS, DEFAULT_MIN_EXP_BACKOFF_TIME_MS); + SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER, + KEY_SYSTEM_STOP_TO_FAILURE_RATIO, + DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO); } private void updateConnectivityConstantsLocked() { @@ -797,6 +809,7 @@ public class JobSchedulerService extends com.android.server.SystemService pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println(); pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println(); + pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println(); pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println(); pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println(); @@ -1277,7 +1290,7 @@ public class JobSchedulerService extends com.android.server.SystemService jobStatus.getJob().isPrefetch(), jobStatus.getJob().getPriority(), jobStatus.getEffectivePriority(), - jobStatus.getNumFailures()); + jobStatus.getNumPreviousAttempts()); // If the job is immediately ready to run, then we can just immediately // put it in the pending list and try to schedule it. This is especially @@ -1476,7 +1489,7 @@ public class JobSchedulerService extends com.android.server.SystemService cancelled.getJob().isPrefetch(), cancelled.getJob().getPriority(), cancelled.getEffectivePriority(), - cancelled.getNumFailures()); + cancelled.getNumPreviousAttempts()); } // If this is a replacement, bring in the new version of the job if (incomingJob != null) { @@ -1820,12 +1833,13 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus); } jobStatus.enqueueTime = sElapsedRealtimeClock.millis(); - final boolean update = mJobs.add(jobStatus); + final boolean update = lastJob != null; + mJobs.add(jobStatus); if (mReadyToRock) { for (int i = 0; i < mControllers.size(); i++) { StateController controller = mControllers.get(i); if (update) { - controller.maybeStopTrackingJobLocked(jobStatus, null, true); + controller.maybeStopTrackingJobLocked(jobStatus, null); } controller.maybeStartTrackingJobLocked(jobStatus, lastJob); } @@ -1858,7 +1872,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (mReadyToRock) { for (int i = 0; i < mControllers.size(); i++) { StateController controller = mControllers.get(i); - controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false); + controller.maybeStopTrackingJobLocked(jobStatus, incomingJob); } } return removed; @@ -1903,7 +1917,7 @@ public class JobSchedulerService extends com.android.server.SystemService * Reschedules the given job based on the job's backoff policy. It doesn't make sense to * specify an override deadline on a failed job (the failed job will run even though it's not * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any - * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed. + * ready job with {@link JobStatus#getNumPreviousAttempts()} > 0 will be executed. * * @param failureToReschedule Provided job status that we will reschedule. * @return A newly instantiated JobStatus with the same constraints as the last job except @@ -1911,12 +1925,24 @@ public class JobSchedulerService extends com.android.server.SystemService * @see #maybeQueueReadyJobsForExecutionLocked */ @VisibleForTesting - JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) { + JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule, + int internalStopReason) { final long elapsedNowMillis = sElapsedRealtimeClock.millis(); final JobInfo job = failureToReschedule.getJob(); final long initialBackoffMillis = job.getInitialBackoffMillis(); - final int backoffAttempts = failureToReschedule.getNumFailures() + 1; + int numFailures = failureToReschedule.getNumFailures(); + int numSystemStops = failureToReschedule.getNumSystemStops(); + // We should back off slowly if JobScheduler keeps stopping the job, + // but back off immediately if the issue appeared to be the app's fault. + if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH + || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT) { + numFailures++; + } else { + numSystemStops++; + } + final int backoffAttempts = Math.max(1, + numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO); long delayMillis; switch (job.getBackoffPolicy()) { @@ -1943,7 +1969,7 @@ public class JobSchedulerService extends com.android.server.SystemService Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS); JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis, - JobStatus.NO_LATEST_RUNTIME, backoffAttempts, + JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops, failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis()); if (job.isPeriodic()) { newJob.setOriginalLatestRunTimeElapsed( @@ -2034,7 +2060,7 @@ public class JobSchedulerService extends com.android.server.SystemService + newLatestRuntimeElapsed); return new JobStatus(periodicToReschedule, elapsedNow + period - flex, elapsedNow + period, - 0 /* backoffAttempt */, + 0 /* numFailures */, 0 /* numSystemStops */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } @@ -2049,7 +2075,7 @@ public class JobSchedulerService extends com.android.server.SystemService } return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed, newLatestRuntimeElapsed, - 0 /* backoffAttempt */, + 0 /* numFailures */, 0 /* numSystemStops */, sSystemClock.millis() /* lastSuccessfulRunTime */, periodicToReschedule.getLastFailedRunTime()); } @@ -2093,7 +2119,7 @@ public class JobSchedulerService extends com.android.server.SystemService // job so we can transfer any appropriate state over from the previous job when // we stop it. final JobStatus rescheduledJob = needsReschedule - ? getRescheduleJobForFailureLocked(jobStatus) : null; + ? getRescheduleJobForFailureLocked(jobStatus, debugStopReason) : null; if (rescheduledJob != null && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT || debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) { @@ -2427,7 +2453,7 @@ public class JobSchedulerService extends com.android.server.SystemService shouldForceBatchJob = mPrefetchController.getNextEstimatedLaunchTimeLocked(job) > relativelySoonCutoffTime; - } else if (job.getNumFailures() > 0) { + } else if (job.getNumPreviousAttempts() > 0) { shouldForceBatchJob = false; } else { final long nowElapsed = sElapsedRealtimeClock.millis(); 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 d6456f0edea5..9e3f19de56f1 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -363,7 +363,7 @@ public final class JobServiceContext implements ServiceConnection { job.getJob().isPrefetch(), job.getJob().getPriority(), job.getEffectivePriority(), - job.getNumFailures()); + job.getNumPreviousAttempts()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { // Use the context's ID to distinguish traces since there'll only be one job // running per context. @@ -1032,7 +1032,7 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getJob().isPrefetch(), completedJob.getJob().getPriority(), completedJob.getEffectivePriority(), - completedJob.getNumFailures()); + completedJob.getNumPreviousAttempts()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", completedJob.getTag(), getId()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 22b09683187f..68cb049af758 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -194,7 +194,7 @@ public final class JobStore { convertRtcBoundsToElapsed(utcTimes, elapsedNow); JobStatus newJob = new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second, - 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()); + 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()); newJob.prepareLocked(); toAdd.add(newJob); toRemove.add(job); @@ -203,13 +203,12 @@ public final class JobStore { } /** - * Add a job to the master list, persisting it if necessary. If the JobStatus already exists, - * it will be replaced. + * Add a job to the master list, persisting it if necessary. + * Similar jobs to the new job will not be removed. + * * @param jobStatus Job to add. - * @return Whether or not an equivalent JobStatus was replaced by this operation. */ - public boolean add(JobStatus jobStatus) { - boolean replaced = mJobSet.remove(jobStatus); + public void add(JobStatus jobStatus) { mJobSet.add(jobStatus); if (jobStatus.isPersisted()) { maybeWriteStatusToDiskAsync(); @@ -217,7 +216,6 @@ public final class JobStore { if (DEBUG) { Slog.d(TAG, "Added job status to store: " + jobStatus); } - return replaced; } /** diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index 65d712102e94..ecee10a13c1d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -81,8 +81,7 @@ public final class BackgroundJobsController extends StateController { } @Override - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index d284a99a4559..2ca3f8f731e9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -133,7 +133,7 @@ public final class BatteryController extends RestrictingController { } @Override - public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { mTrackedTasks.remove(taskStatus); mTopStartedJobs.remove(taskStatus); @@ -143,7 +143,7 @@ public final class BatteryController extends RestrictingController { @Override public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) { if (!jobStatus.hasPowerConstraint()) { - maybeStopTrackingJobLocked(jobStatus, null, false); + maybeStopTrackingJobLocked(jobStatus, null); } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java index 9b5956094e48..b029e0075dc2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java @@ -127,8 +127,7 @@ public class ComponentController extends StateController { } @Override - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { } @Override diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index d2dc2a7e4e96..16dd16727fa6 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -290,8 +290,7 @@ public final class ConnectivityController extends RestrictingController implemen @GuardedBy("mLock") @Override - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) { ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid()); if (jobs != null) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java index 83a756cf1e11..847a1bfe4465 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java @@ -159,8 +159,7 @@ public final class ContentObserverController extends StateController { } @Override - public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_CONTENT)) { mTrackedTasks.remove(taskStatus); if (taskStatus.contentObserverJobInstance != null) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java index abbe177c5d49..bdf72b64d3e0 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java @@ -225,8 +225,7 @@ public final class DeviceIdleJobsController extends StateController { } @Override - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) { mAllowInIdleJobs.remove(jobStatus); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 547f94badd69..4c176921ed14 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -213,7 +213,7 @@ public final class FlexibilityController extends StateController { @Override @GuardedBy("mLock") - public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob, boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) { if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) { mFlexibilityAlarmQueue.removeAlarmForKey(js); mFlexibilityTracker.remove(js); @@ -342,10 +342,10 @@ public final class FlexibilityController extends StateController { // There is no deadline and no estimated launch time. return NO_LIFECYCLE_END; } - if (js.getNumFailures() > 1) { - // Number of failures will not equal one as per restriction in JobStatus constructor. + // Increase the flex deadline for jobs rescheduled more than once. + if (js.getNumPreviousAttempts() > 1) { return earliest + Math.min( - (long) Math.scalb(mRescheduledJobDeadline, js.getNumFailures() - 2), + (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2), mMaxRescheduledDeadline); } return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java index 926cfc192cd1..a25af7110ee5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java @@ -76,8 +76,7 @@ public final class IdleController extends RestrictingController implements Idlen } @Override - public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) { mTrackedTasks.remove(taskStatus); } @@ -86,7 +85,7 @@ public final class IdleController extends RestrictingController implements Idlen @Override public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) { if (!jobStatus.hasIdleConstraint()) { - maybeStopTrackingJobLocked(jobStatus, null, false); + maybeStopTrackingJobLocked(jobStatus, null); } } 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 de602a8f7f25..f9fb0d0df002 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 @@ -241,10 +241,22 @@ public final class JobStatus { */ private long mOriginalLatestRunTimeElapsedMillis; - /** How many times this job has failed, used to compute back-off. */ + /** + * How many times this job has failed to complete on its own + * (via {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} or because of + * a timeout). + * This count doesn't include most times JobScheduler decided to stop the job + * (via {@link android.app.job.JobService#onStopJob(JobParameters)}. + */ private final int numFailures; /** + * The number of times JobScheduler has forced this job to stop due to reasons mostly outside + * of the app's control. + */ + private final int mNumSystemStops; + + /** * Which app standby bucket this job's app is in. Updated when the app is moved to a * different bucket. */ @@ -488,6 +500,8 @@ public final class JobStatus { * @param tag A string associated with the job for debugging/logging purposes. * @param numFailures Count of how many times this job has requested a reschedule because * its work was not yet finished. + * @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to + * factors mostly out of the app's control. * @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job * is to be considered runnable * @param latestRunTimeElapsedMillis Milestone: point in time at which the job will be @@ -497,7 +511,7 @@ public final class JobStatus { * @param internalFlags Non-API property flags about this job */ private JobStatus(JobInfo job, int callingUid, String sourcePackageName, - int sourceUserId, int standbyBucket, String tag, int numFailures, + int sourceUserId, int standbyBucket, String tag, int numFailures, int numSystemStops, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags, int dynamicConstraints) { @@ -535,6 +549,7 @@ public final class JobStatus { this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis; this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis; this.numFailures = numFailures; + mNumSystemStops = numSystemStops; int requiredConstraints = job.getConstraintFlags(); if (job.getRequiredNetwork() != null) { @@ -576,7 +591,7 @@ public final class JobStatus { // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline. if (!isRequestedExpeditedJob() && satisfiesMinWindowException - && numFailures != 1 + && (numFailures + numSystemStops) != 1 && lacksSomeFlexibleConstraints) { mNumRequiredFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); @@ -626,7 +641,7 @@ public final class JobStatus { this(jobStatus.getJob(), jobStatus.getUid(), jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(), jobStatus.getStandbyBucket(), - jobStatus.getSourceTag(), jobStatus.getNumFailures(), + jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(), jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(), jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(), jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints); @@ -654,7 +669,7 @@ public final class JobStatus { int innerFlags, int dynamicConstraints) { this(job, callingUid, sourcePkgName, sourceUserId, standbyBucket, - sourceTag, 0, + sourceTag, /* numFailures */ 0, /* numSystemStops */ 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints); @@ -673,12 +688,13 @@ public final class JobStatus { /** Create a new job to be rescheduled with the provided parameters. */ public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis, - long newLatestRuntimeElapsedMillis, int backoffAttempt, + long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops, long lastSuccessfulRunTime, long lastFailedRunTime) { this(rescheduling.job, rescheduling.getUid(), rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(), rescheduling.getStandbyBucket(), - rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis, + rescheduling.getSourceTag(), numFailures, numSystemStops, + newEarliestRuntimeElapsedMillis, newLatestRuntimeElapsedMillis, lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(), rescheduling.mDynamicConstraints); @@ -715,7 +731,7 @@ public final class JobStatus { int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage, sourceUserId, elapsedNow); return new JobStatus(job, callingUid, sourcePkg, sourceUserId, - standbyBucket, tag, 0, + standbyBucket, tag, /* numFailures */ 0, /* numSystemStops */ 0, earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */, /*innerFlags=*/ 0, /* dynamicConstraints */ 0); @@ -868,10 +884,27 @@ public final class JobStatus { pw.print(job.getId()); } + /** + * Returns the number of times the job stopped previously for reasons that appeared to be within + * the app's control. + */ public int getNumFailures() { return numFailures; } + /** + * Returns the number of times the system stopped a previous execution of this job for reasons + * that were likely outside the app's control. + */ + public int getNumSystemStops() { + return mNumSystemStops; + } + + /** Returns the total number of times we've attempted to run this job in the past. */ + public int getNumPreviousAttempts() { + return numFailures + mNumSystemStops; + } + public ComponentName getServiceComponent() { return job.getService(); } @@ -1857,6 +1890,10 @@ public final class JobStatus { sb.append(" failures="); sb.append(numFailures); } + if (mNumSystemStops != 0) { + sb.append(" system stops="); + sb.append(mNumSystemStops); + } if (isReady()) { sb.append(" READY"); } else { @@ -2382,6 +2419,9 @@ public final class JobStatus { if (numFailures != 0) { pw.print("Num failures: "); pw.println(numFailures); } + if (mNumSystemStops != 0) { + pw.print("Num system stops: "); pw.println(mNumSystemStops); + } if (mLastSuccessfulRunTime != 0) { pw.print("Last successful run: "); pw.println(formatTime(mLastSuccessfulRunTime)); @@ -2579,7 +2619,7 @@ public final class JobStatus { proto.write(JobStatusDumpProto.ORIGINAL_LATEST_RUNTIME_ELAPSED, mOriginalLatestRunTimeElapsedMillis); - proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures); + proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures + mNumSystemStops); proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime); proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java deleted file mode 100644 index 78a77fe46f3c..000000000000 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.job.controllers; - -import java.util.Objects; - -/** Wrapper class to represent a userId-pkgName combo. */ -final class Package { - public final String packageName; - public final int userId; - - Package(int userId, String packageName) { - this.userId = userId; - this.packageName = packageName; - } - - @Override - public String toString() { - return packageToString(userId, packageName); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Package)) { - return false; - } - Package other = (Package) obj; - return userId == other.userId && Objects.equals(packageName, other.packageName); - } - - @Override - public int hashCode() { - return packageName.hashCode() + userId; - } - - /** - * Standardize the output of userId-packageName combo. - */ - static String packageToString(int userId, String packageName) { - return "<" + userId + ">" + packageName; - } -} diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java index e04cec30d26b..c46ffd7c06e9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java @@ -21,7 +21,6 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static com.android.server.job.JobSchedulerService.sSystemClock; -import static com.android.server.job.controllers.Package.packageToString; import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; @@ -31,6 +30,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener; import android.appwidget.AppWidgetManager; import android.content.Context; +import android.content.pm.UserPackage; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -167,13 +167,12 @@ public class PrefetchController extends StateController { @Override @GuardedBy("mLock") - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { final int userId = jobStatus.getSourceUserId(); final String pkgName = jobStatus.getSourcePackageName(); final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) { - mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName)); + mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName)); } } @@ -187,7 +186,7 @@ public class PrefetchController extends StateController { final int userId = UserHandle.getUserId(uid); mTrackedJobs.delete(userId, packageName); mEstimatedLaunchTimes.delete(userId, packageName); - mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName)); + mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, packageName)); } @Override @@ -355,7 +354,7 @@ public class PrefetchController extends StateController { @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) { final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); if (jobs == null || jobs.size() == 0) { - mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName)); + mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName)); return; } @@ -366,10 +365,10 @@ public class PrefetchController extends StateController { // Set alarm to be notified when this crosses the threshold. final long timeToCrossThresholdMs = nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs); - mThresholdAlarmListener.addAlarm(new Package(userId, pkgName), + mThresholdAlarmListener.addAlarm(UserPackage.of(userId, pkgName), nowElapsed + timeToCrossThresholdMs); } else { - mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName)); + mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName)); } } @@ -428,25 +427,25 @@ public class PrefetchController extends StateController { } /** Track when apps will cross the "will run soon" threshold. */ - private class ThresholdAlarmListener extends AlarmQueue<Package> { + private class ThresholdAlarmListener extends AlarmQueue<UserPackage> { private ThresholdAlarmListener(Context context, Looper looper) { super(context, looper, "*job.prefetch*", "Prefetch threshold", false, PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10); } @Override - protected boolean isForUser(@NonNull Package key, int userId) { + protected boolean isForUser(@NonNull UserPackage key, int userId) { return key.userId == userId; } @Override - protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) { + protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) { final ArraySet<JobStatus> changedJobs = new ArraySet<>(); synchronized (mLock) { final long now = sSystemClock.millis(); final long nowElapsed = sElapsedRealtimeClock.millis(); for (int i = 0; i < expired.size(); ++i) { - Package p = expired.valueAt(i); + UserPackage p = expired.valueAt(i); if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) { Slog.e(TAG, "Alarm expired for " + packageToString(p.userId, p.packageName) + " at the wrong time"); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index bb8d175c2375..d8206ad028a9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -28,7 +28,6 @@ import static com.android.server.job.JobSchedulerService.RARE_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; -import static com.android.server.job.controllers.Package.packageToString; import android.Manifest; import android.annotation.NonNull; @@ -44,6 +43,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.UserPackage; import android.os.BatteryManager; import android.os.Handler; import android.os.Looper; @@ -532,7 +532,7 @@ public final class QuotaController extends StateController { */ private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>(); - /** An app has reached its quota. The message should contain a {@link Package} object. */ + /** An app has reached its quota. The message should contain a {@link UserPackage} object. */ @VisibleForTesting static final int MSG_REACHED_QUOTA = 0; /** Drop any old timing sessions. */ @@ -542,7 +542,7 @@ public final class QuotaController extends StateController { /** Process state for a UID has changed. */ private static final int MSG_UID_PROCESS_STATE_CHANGED = 3; /** - * An app has reached its expedited job quota. The message should contain a {@link Package} + * An app has reached its expedited job quota. The message should contain a {@link UserPackage} * object. */ @VisibleForTesting @@ -673,15 +673,14 @@ public final class QuotaController extends StateController { @Override @GuardedBy("mLock") - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) { unprepareFromExecutionLocked(jobStatus); final int userId = jobStatus.getSourceUserId(); final String pkgName = jobStatus.getSourcePackageName(); ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName); if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) { - mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); + mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName)); } } } @@ -747,7 +746,7 @@ public final class QuotaController extends StateController { } mTimingEvents.delete(userId, packageName); mEJTimingSessions.delete(userId, packageName); - mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); + mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); mExecutionStatsCache.delete(userId, packageName); mEJStats.delete(userId, packageName); mTopAppTrackers.delete(userId, packageName); @@ -1726,7 +1725,7 @@ public final class QuotaController extends StateController { // exempted. maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket); } else { - mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); + mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); } return changedJobs; } @@ -1765,7 +1764,7 @@ public final class QuotaController extends StateController { && isWithinQuotaLocked(userId, packageName, realStandbyBucket)) { // TODO(141645789): we probably shouldn't cancel the alarm until we've verified // that all jobs for the userId-package are within quota. - mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); + mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); } else { mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket); } @@ -1815,7 +1814,7 @@ public final class QuotaController extends StateController { if (jobs == null || jobs.size() == 0) { Slog.e(TAG, "maybeScheduleStartAlarmLocked called for " + packageToString(userId, packageName) + " that has no jobs"); - mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); + mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); return; } @@ -1839,7 +1838,7 @@ public final class QuotaController extends StateController { + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket) + "ms in its quota."); } - mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName)); + mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName)); mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); return; } @@ -1904,7 +1903,7 @@ public final class QuotaController extends StateController { + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats); inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS; } - mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed); + mInQuotaAlarmQueue.addAlarm(UserPackage.of(userId, packageName), inQuotaTimeElapsed); } private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed, @@ -2099,7 +2098,7 @@ public final class QuotaController extends StateController { } private final class Timer { - private final Package mPkg; + private final UserPackage mPkg; private final int mUid; private final boolean mRegularJobTimer; @@ -2111,7 +2110,7 @@ public final class QuotaController extends StateController { private long mDebitAdjustment; Timer(int uid, int userId, String packageName, boolean regularJobTimer) { - mPkg = new Package(userId, packageName); + mPkg = UserPackage.of(userId, packageName); mUid = uid; mRegularJobTimer = regularJobTimer; } @@ -2366,7 +2365,7 @@ public final class QuotaController extends StateController { } private final class TopAppTimer { - private final Package mPkg; + private final UserPackage mPkg; // List of jobs currently running for this app that started when the app wasn't in the // foreground. @@ -2374,7 +2373,7 @@ public final class QuotaController extends StateController { private long mStartTimeElapsed; TopAppTimer(int userId, String packageName) { - mPkg = new Package(userId, packageName); + mPkg = UserPackage.of(userId, packageName); } private int calculateTimeChunks(final long nowElapsed) { @@ -2657,7 +2656,7 @@ public final class QuotaController extends StateController { synchronized (mLock) { switch (msg.what) { case MSG_REACHED_QUOTA: { - Package pkg = (Package) msg.obj; + UserPackage pkg = (UserPackage) msg.obj; if (DEBUG) { Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); } @@ -2686,7 +2685,7 @@ public final class QuotaController extends StateController { break; } case MSG_REACHED_EJ_QUOTA: { - Package pkg = (Package) msg.obj; + UserPackage pkg = (UserPackage) msg.obj; if (DEBUG) { Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota."); } @@ -2888,21 +2887,21 @@ public final class QuotaController extends StateController { } /** Track when UPTCs are expected to come back into quota. */ - private class InQuotaAlarmQueue extends AlarmQueue<Package> { + private class InQuotaAlarmQueue extends AlarmQueue<UserPackage> { private InQuotaAlarmQueue(Context context, Looper looper) { super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false, QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS); } @Override - protected boolean isForUser(@NonNull Package key, int userId) { + protected boolean isForUser(@NonNull UserPackage key, int userId) { return key.userId == userId; } @Override - protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) { + protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) { for (int i = 0; i < expired.size(); ++i) { - Package p = expired.valueAt(i); + UserPackage p = expired.valueAt(i); mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName) .sendToTarget(); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java index 8453e53782ca..44ac798c2912 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java @@ -85,8 +85,7 @@ public abstract class StateController { /** * Remove task - this will happen if the task is cancelled, completed, etc. */ - public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate); + public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob); /** * Called when a new job is being created to reschedule an old failed job. @@ -187,4 +186,11 @@ public abstract class StateController { /** Dump any internal constants the Controller may have. */ public void dumpConstants(ProtoOutputStream proto) { } + + /** + * Standardize the output of userId-packageName combo. + */ + static String packageToString(int userId, String packageName) { + return "<" + userId + ">" + packageName; + } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java index 1ce0a7f6b4c7..11e2ff7bd77f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java @@ -70,8 +70,7 @@ public final class StorageController extends StateController { } @Override - public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) { if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) { mTrackedTasks.remove(taskStatus); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java index b2ca3a051e36..cafb02dd7531 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java @@ -383,8 +383,7 @@ public class TareController extends StateController { @Override @GuardedBy("mLock") - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { final int userId = jobStatus.getSourceUserId(); final String pkgName = jobStatus.getSourcePackageName(); if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java index b6361ce56569..5195f28de9d2 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java @@ -79,7 +79,7 @@ public final class TimeController extends StateController { @Override public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) { if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) { - maybeStopTrackingJobLocked(job, null, false); + maybeStopTrackingJobLocked(job, null); // First: check the constraints now, because if they are already satisfied // then there is no need to track it. This gives us a fast path for a common @@ -134,8 +134,7 @@ public final class TimeController extends StateController { * tracking was the one our alarms were based off of. */ @Override - public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob) { if (job.clearTrackingController(JobStatus.TRACKING_TIME)) { if (mTrackedJobs.remove(job)) { checkExpiredDelaysAndResetAlarm(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java index 7a13e3f1fad7..abc196f36d7c 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java @@ -36,6 +36,7 @@ import static com.android.server.tare.TareUtils.getCurrentTimeMillis; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.UserPackage; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -824,7 +825,7 @@ class Agent { void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) { mScribe.discardLedgerLocked(userId, pkgName); mCurrentOngoingEvents.delete(userId, pkgName); - mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); + mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName)); } @GuardedBy("mLock") @@ -959,7 +960,7 @@ class Agent { mCurrentOngoingEvents.get(userId, pkgName); if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) { // No ongoing transactions. No reason to schedule - mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); + mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName)); return; } mTrendCalculator.reset(getBalanceLocked(userId, pkgName), @@ -972,7 +973,7 @@ class Agent { if (lowerTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) { if (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) { // Will never cross a threshold based on current events. - mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName)); + mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName)); return; } timeToThresholdMs = upperTimeMs; @@ -980,7 +981,7 @@ class Agent { timeToThresholdMs = (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) ? lowerTimeMs : Math.min(lowerTimeMs, upperTimeMs); } - mBalanceThresholdAlarmQueue.addAlarm(new Package(userId, pkgName), + mBalanceThresholdAlarmQueue.addAlarm(UserPackage.of(userId, pkgName), SystemClock.elapsedRealtime() + timeToThresholdMs); } @@ -1071,57 +1072,22 @@ class Agent { private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater(); - private static final class Package { - public final String packageName; - public final int userId; - - Package(int userId, String packageName) { - this.userId = userId; - this.packageName = packageName; - } - - @Override - public String toString() { - return appToString(userId, packageName); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (obj instanceof Package) { - Package other = (Package) obj; - return userId == other.userId && Objects.equals(packageName, other.packageName); - } - return false; - } - - @Override - public int hashCode() { - return packageName.hashCode() + userId; - } - } - /** Track when apps will cross the closest affordability threshold (in both directions). */ - private class BalanceThresholdAlarmQueue extends AlarmQueue<Package> { + private class BalanceThresholdAlarmQueue extends AlarmQueue<UserPackage> { private BalanceThresholdAlarmQueue(Context context, Looper looper) { super(context, looper, ALARM_TAG_AFFORDABILITY_CHECK, "Affordability check", true, 15_000L); } @Override - protected boolean isForUser(@NonNull Package key, int userId) { + protected boolean isForUser(@NonNull UserPackage key, int userId) { return key.userId == userId; } @Override - protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) { + protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) { for (int i = 0; i < expired.size(); ++i) { - Package p = expired.valueAt(i); + UserPackage p = expired.valueAt(i); mHandler.obtainMessage( MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName) .sendToTarget(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java index b426f16744e3..46338fa69eb0 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java @@ -33,9 +33,10 @@ import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NO import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES; -import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES; @@ -71,9 +72,10 @@ import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAK import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE; import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP; -import static android.app.tare.EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT; +import static android.app.tare.EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP; import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT; @@ -146,7 +148,8 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedBalanceOther; private long mMaxSatiatedBalance; private long mInitialSatiatedConsumptionLimit; - private long mHardSatiatedConsumptionLimit; + private long mMinSatiatedConsumptionLimit; + private long mMaxSatiatedConsumptionLimit; private final KeyValueListParser mParser = new KeyValueListParser(','); private final Injector mInjector; @@ -199,8 +202,13 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { } @Override - long getHardSatiatedConsumptionLimit() { - return mHardSatiatedConsumptionLimit; + long getMinSatiatedConsumptionLimit() { + return mMinSatiatedConsumptionLimit; + } + + @Override + long getMaxSatiatedConsumptionLimit() { + return mMaxSatiatedConsumptionLimit; } @NonNull @@ -240,12 +248,15 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { mMaxSatiatedBalance = getConstantAsCake(mParser, properties, KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES, Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); + mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_AM_MIN_CONSUMPTION_LIMIT, DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES, + arcToCake(1)); mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, - KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, - arcToCake(1)); - mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, - KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, - mInitialSatiatedConsumptionLimit); + KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, + mMinSatiatedConsumptionLimit); + mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_AM_MAX_CONSUMPTION_LIMIT, DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES, + mInitialSatiatedConsumptionLimit); final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties, KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE, @@ -396,9 +407,11 @@ public class AlarmManagerEconomicPolicy extends EconomicPolicy { pw.decreaseIndent(); pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println(); pw.print("Consumption limits: ["); + pw.print(cakeToString(mMinSatiatedConsumptionLimit)); + pw.print(", "); pw.print(cakeToString(mInitialSatiatedConsumptionLimit)); pw.print(", "); - pw.print(cakeToString(mHardSatiatedConsumptionLimit)); + pw.print(cakeToString(mMaxSatiatedConsumptionLimit)); pw.println("]"); pw.println(); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java index 66f7c357d223..7a9607657972 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java @@ -43,7 +43,8 @@ public class CompleteEconomicPolicy extends EconomicPolicy { private int mEnabledEconomicPolicyIds = 0; private int[] mCostModifiers = EmptyArray.INT; private long mInitialConsumptionLimit; - private long mHardConsumptionLimit; + private long mMinConsumptionLimit; + private long mMaxConsumptionLimit; CompleteEconomicPolicy(@NonNull InternalResourceService irs) { this(irs, new CompleteInjector()); @@ -100,14 +101,17 @@ public class CompleteEconomicPolicy extends EconomicPolicy { private void updateLimits() { long initialConsumptionLimit = 0; - long hardConsumptionLimit = 0; + long minConsumptionLimit = 0; + long maxConsumptionLimit = 0; for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) { final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i); initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit(); - hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit(); + minConsumptionLimit += economicPolicy.getMinSatiatedConsumptionLimit(); + maxConsumptionLimit += economicPolicy.getMaxSatiatedConsumptionLimit(); } mInitialConsumptionLimit = initialConsumptionLimit; - mHardConsumptionLimit = hardConsumptionLimit; + mMinConsumptionLimit = minConsumptionLimit; + mMaxConsumptionLimit = maxConsumptionLimit; } @Override @@ -134,8 +138,13 @@ public class CompleteEconomicPolicy extends EconomicPolicy { } @Override - long getHardSatiatedConsumptionLimit() { - return mHardConsumptionLimit; + long getMinSatiatedConsumptionLimit() { + return mMinConsumptionLimit; + } + + @Override + long getMaxSatiatedConsumptionLimit() { + return mMaxConsumptionLimit; } @NonNull diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java index 008dcb8edf63..b52f6f11b3bf 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java @@ -232,15 +232,21 @@ public abstract class EconomicPolicy { * Returns the maximum number of cakes that should be consumed during a full 100% discharge * cycle. This is the initial limit. The system may choose to increase the limit over time, * but the increased limit should never exceed the value returned from - * {@link #getHardSatiatedConsumptionLimit()}. + * {@link #getMaxSatiatedConsumptionLimit()}. */ abstract long getInitialSatiatedConsumptionLimit(); /** - * Returns the maximum number of cakes that should be consumed during a full 100% discharge - * cycle. This is the hard limit that should never be exceeded. + * Returns the minimum number of cakes that should be available for consumption during a full + * 100% discharge cycle. + */ + abstract long getMinSatiatedConsumptionLimit(); + + /** + * Returns the maximum number of cakes that should be available for consumption during a full + * 100% discharge cycle. */ - abstract long getHardSatiatedConsumptionLimit(); + abstract long getMaxSatiatedConsumptionLimit(); /** Return the set of modifiers that should apply to this policy's costs. */ @NonNull diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index dd0a19433683..581a545f8401 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -670,7 +670,7 @@ public class InternalResourceService extends SystemService { final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD) * currentConsumptionLimit / 100; final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall, - mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()); + mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()); if (newConsumptionLimit != currentConsumptionLimit) { Slog.i(TAG, "Increasing consumption limit from " + cakeToString(currentConsumptionLimit) + " to " + cakeToString(newConsumptionLimit)); @@ -720,12 +720,12 @@ public class InternalResourceService extends SystemService { // The stock is too low. We're doing pretty well. We can increase the stock slightly // to let apps do more work in the background. newConsumptionLimit = Math.min((long) (currentConsumptionLimit * 1.01), - mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()); + mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()); } else if (percentageOfTarget < 100) { // The stock is too high IMO. We're below the target. Decrease the stock to reduce // background work. newConsumptionLimit = Math.max((long) (currentConsumptionLimit * .98), - mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); + mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit()); } else { // The stock is just right. return; @@ -957,9 +957,9 @@ public class InternalResourceService extends SystemService { } else { mScribe.loadFromDiskLocked(); if (mScribe.getSatiatedConsumptionLimitLocked() - < mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() + < mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit() || mScribe.getSatiatedConsumptionLimitLocked() - > mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { + > mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()) { // Reset the consumption limit since several factors may have changed. mScribe.setConsumptionLimitLocked( mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); @@ -1442,17 +1442,16 @@ public class InternalResourceService extends SystemService { private void updateEconomicPolicy() { synchronized (mLock) { - final long initialLimit = - mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit(); - final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit(); + final long minLimit = mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit(); + final long maxLimit = mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit(); final int oldEnabledPolicies = mCompleteEconomicPolicy.getEnabledPolicyIds(); mCompleteEconomicPolicy.tearDown(); mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this); if (mIsEnabled && mBootPhase >= PHASE_THIRD_PARTY_APPS_CAN_START) { mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties()); - if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() - || hardLimit - != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { + if (minLimit != mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit() + || maxLimit + != mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()) { // Reset the consumption limit since several factors may have changed. mScribe.setConsumptionLimitLocked( mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java index 71c6d099ac77..7cf459c2e494 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java @@ -38,9 +38,10 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_BA import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES; -import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES; +import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES; import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES; @@ -84,9 +85,10 @@ import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_BASE_P import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE; import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP; -import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT; +import static android.app.tare.EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE; +import static android.app.tare.EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER; import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP; @@ -159,7 +161,8 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { private long mMinSatiatedBalanceIncrementalAppUpdater; private long mMaxSatiatedBalance; private long mInitialSatiatedConsumptionLimit; - private long mHardSatiatedConsumptionLimit; + private long mMinSatiatedConsumptionLimit; + private long mMaxSatiatedConsumptionLimit; private final KeyValueListParser mParser = new KeyValueListParser(','); private final Injector mInjector; @@ -216,8 +219,13 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { } @Override - long getHardSatiatedConsumptionLimit() { - return mHardSatiatedConsumptionLimit; + long getMinSatiatedConsumptionLimit() { + return mMinSatiatedConsumptionLimit; + } + + @Override + long getMaxSatiatedConsumptionLimit() { + return mMaxSatiatedConsumptionLimit; } @NonNull @@ -260,12 +268,15 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { mMaxSatiatedBalance = getConstantAsCake(mParser, properties, KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, Math.max(arcToCake(1), mMinSatiatedBalanceExempted)); + mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_JS_MIN_CONSUMPTION_LIMIT, DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES, + arcToCake(1)); mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, - KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, - arcToCake(1)); - mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, - KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES, - mInitialSatiatedConsumptionLimit); + KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, + mMinSatiatedConsumptionLimit); + mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties, + KEY_JS_MAX_CONSUMPTION_LIMIT, DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES, + mInitialSatiatedConsumptionLimit); mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START, getConstantAsCake(mParser, properties, @@ -420,9 +431,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy { pw.decreaseIndent(); pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println(); pw.print("Consumption limits: ["); + pw.print(cakeToString(mMinSatiatedConsumptionLimit)); + pw.print(", "); pw.print(cakeToString(mInitialSatiatedConsumptionLimit)); pw.print(", "); - pw.print(cakeToString(mHardSatiatedConsumptionLimit)); + pw.print(cakeToString(mMaxSatiatedConsumptionLimit)); pw.println("]"); pw.println(); diff --git a/cmds/uiautomator/OWNERS b/cmds/uiautomator/OWNERS new file mode 100644 index 000000000000..5c7f45230ff3 --- /dev/null +++ b/cmds/uiautomator/OWNERS @@ -0,0 +1,4 @@ +# Bug component: 833089 +peykov@google.com +normancheung@google.com +guran@google.com diff --git a/core/api/current.txt b/core/api/current.txt index 218d7bd14ceb..3174c2865346 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -143,6 +143,7 @@ package android { field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO"; field public static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES"; field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO"; + field public static final String READ_MEDIA_VISUAL_USER_SELECTED = "android.permission.READ_MEDIA_VISUAL_USER_SELECTED"; field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY"; field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS"; field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; @@ -4604,23 +4605,24 @@ package android.app { public class AlarmManager { method public boolean canScheduleExactAlarms(); - method public void cancel(android.app.PendingIntent); - method public void cancel(android.app.AlarmManager.OnAlarmListener); + method public void cancel(@NonNull android.app.PendingIntent); + method public void cancel(@NonNull android.app.AlarmManager.OnAlarmListener); method public void cancelAll(); method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock(); - method public void set(int, long, android.app.PendingIntent); - method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler); - method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent); - method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent); - method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, android.app.PendingIntent); - method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler); - method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent); - method public void setInexactRepeating(int, long, long, android.app.PendingIntent); - method public void setRepeating(int, long, long, android.app.PendingIntent); + method public void set(int, long, @NonNull android.app.PendingIntent); + method public void set(int, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler); + method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(@NonNull android.app.AlarmManager.AlarmClockInfo, @NonNull android.app.PendingIntent); + method public void setAndAllowWhileIdle(int, long, @NonNull android.app.PendingIntent); + method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, @NonNull android.app.PendingIntent); + method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler); + method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, @NonNull android.app.PendingIntent); + method public void setInexactRepeating(int, long, long, @NonNull android.app.PendingIntent); + method public void setRepeating(int, long, long, @NonNull android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long); method @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) public void setTimeZone(String); - method public void setWindow(int, long, long, android.app.PendingIntent); - method public void setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler); + method public void setWindow(int, long, long, @NonNull android.app.PendingIntent); + method public void setWindow(int, long, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler); + method public void setWindow(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener); field public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED"; field public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED"; field public static final int ELAPSED_REALTIME = 3; // 0x3 @@ -7457,6 +7459,7 @@ package android.app.admin { method public long getMaximumTimeToLock(@Nullable android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName); method public int getMinimumRequiredWifiSecurityLevel(); + method public int getMtePolicy(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy(); method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName); @@ -7605,6 +7608,7 @@ package android.app.admin { method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long); method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); method public void setMinimumRequiredWifiSecurityLevel(int); + method public void setMtePolicy(int); method public void setNearbyAppStreamingPolicy(int); method public void setNearbyNotificationStreamingPolicy(int); method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean); @@ -7788,6 +7792,9 @@ package android.app.admin { field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1 field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2 field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning"; + field public static final int MTE_DISABLED = 2; // 0x2 + field public static final int MTE_ENABLED = 1; // 0x1 + field public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0; // 0x0 field public static final int NEARBY_STREAMING_DISABLED = 1; // 0x1 field public static final int NEARBY_STREAMING_ENABLED = 2; // 0x2 field public static final int NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY = 0; // 0x0 @@ -8949,9 +8956,18 @@ package android.appwidget { package android.companion { + public final class AssociatedDevice implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.bluetooth.le.ScanResult getBleDevice(); + method @Nullable public android.bluetooth.BluetoothDevice getBluetoothDevice(); + method @Nullable public android.net.wifi.ScanResult getWifiDevice(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociatedDevice> CREATOR; + } + public final class AssociationInfo implements android.os.Parcelable { method public int describeContents(); - method @Nullable public android.os.Parcelable getAssociatedDevice(); + method @Nullable public android.companion.AssociatedDevice getAssociatedDevice(); method @Nullable public android.net.MacAddress getDeviceMacAddress(); method @Nullable public String getDeviceProfile(); method @Nullable public CharSequence getDisplayName(); @@ -11649,7 +11665,7 @@ package android.content.pm { public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable { method public int describeContents(); method @Nullable public android.graphics.Bitmap getIcon(); - method @NonNull public String getLabel(); + method @NonNull public CharSequence getLabel(); method @NonNull public android.icu.util.ULocale getLocale(); method @NonNull public String getPackageName(); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -11660,7 +11676,7 @@ package android.content.pm { ctor public PackageInstaller.PreapprovalDetails.Builder(); method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails build(); method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(@NonNull android.graphics.Bitmap); - method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull String); + method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull CharSequence); method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(@NonNull android.icu.util.ULocale); method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(@NonNull String); } @@ -19474,6 +19490,7 @@ package android.location { public final class GnssCapabilities implements android.os.Parcelable { method public int describeContents(); + method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes(); method public boolean hasAntennaInfo(); method public boolean hasGeofencing(); method @Deprecated public boolean hasGnssAntennaInfo(); @@ -19507,6 +19524,7 @@ package android.location { ctor public GnssCapabilities.Builder(); ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities); method @NonNull public android.location.GnssCapabilities build(); + method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>); method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean); method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean); @@ -19719,6 +19737,16 @@ package android.location { field @Deprecated public static final int STATUS_READY = 1; // 0x1 } + public final class GnssSignalType implements android.os.Parcelable { + method @NonNull public static android.location.GnssSignalType create(int, @FloatRange(from=0.0f, fromInclusive=false) double, @NonNull String); + method public int describeContents(); + method @FloatRange(from=0.0f, fromInclusive=false) public double getCarrierFrequencyHz(); + method @NonNull public String getCodeType(); + method public int getConstellationType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssSignalType> CREATOR; + } + public final class GnssStatus implements android.os.Parcelable { method public int describeContents(); method @FloatRange(from=0, to=360) public float getAzimuthDegrees(@IntRange(from=0) int); @@ -32540,6 +32568,7 @@ package android.os { field public static final String DISALLOW_BLUETOOTH = "no_bluetooth"; field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing"; field public static final String DISALLOW_CAMERA_TOGGLE = "disallow_camera_toggle"; + field public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g"; field public static final String DISALLOW_CHANGE_WIFI_STATE = "no_change_wifi_state"; field public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth"; field public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness"; @@ -41586,7 +41615,7 @@ package android.telephony { field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool"; field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string"; field public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL = "carrier_cross_sim_ims_available_bool"; - field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; + field @Deprecated public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array"; field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array"; field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array"; @@ -41723,6 +41752,7 @@ package android.telephony { field public static final String KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL = "enableMMSReadReports"; field public static final String KEY_MMS_MULTIPART_SMS_ENABLED_BOOL = "enableMultipartSMS"; field public static final String KEY_MMS_NAI_SUFFIX_STRING = "naiSuffix"; + field public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT = "mms_network_release_timeout_millis_int"; field public static final String KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL = "enabledNotifyWapMMSC"; field public static final String KEY_MMS_RECIPIENT_LIMIT_INT = "recipientLimit"; field public static final String KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL = "sendMultipartSmsAsSeparateMessages"; @@ -41754,6 +41784,7 @@ package android.telephony { field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array"; + field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long"; field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long"; field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long"; field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long"; @@ -43411,14 +43442,18 @@ package android.telephony { field public static final int RESULT_RECEIVE_WHILE_ENCRYPTED = 504; // 0x1f8 field public static final int RESULT_REMOTE_EXCEPTION = 31; // 0x1f field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18 + field public static final int RESULT_RIL_ABORTED = 137; // 0x89 field public static final int RESULT_RIL_ACCESS_BARRED = 122; // 0x7a field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b field public static final int RESULT_RIL_CANCELLED = 119; // 0x77 + field public static final int RESULT_RIL_DEVICE_IN_USE = 136; // 0x88 field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d field public static final int RESULT_RIL_GENERIC_ERROR = 124; // 0x7c field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71 field public static final int RESULT_RIL_INVALID_ARGUMENTS = 104; // 0x68 field public static final int RESULT_RIL_INVALID_MODEM_STATE = 115; // 0x73 + field public static final int RESULT_RIL_INVALID_RESPONSE = 125; // 0x7d + field public static final int RESULT_RIL_INVALID_SIM_STATE = 130; // 0x82 field public static final int RESULT_RIL_INVALID_SMSC_ADDRESS = 110; // 0x6e field public static final int RESULT_RIL_INVALID_SMS_FORMAT = 107; // 0x6b field public static final int RESULT_RIL_INVALID_STATE = 103; // 0x67 @@ -43427,14 +43462,23 @@ package android.telephony { field public static final int RESULT_RIL_NETWORK_NOT_READY = 116; // 0x74 field public static final int RESULT_RIL_NETWORK_REJECT = 102; // 0x66 field public static final int RESULT_RIL_NO_MEMORY = 105; // 0x69 + field public static final int RESULT_RIL_NO_NETWORK_FOUND = 135; // 0x87 field public static final int RESULT_RIL_NO_RESOURCES = 118; // 0x76 + field public static final int RESULT_RIL_NO_SMS_TO_ACK = 131; // 0x83 + field public static final int RESULT_RIL_NO_SUBSCRIPTION = 134; // 0x86 field public static final int RESULT_RIL_OPERATION_NOT_ALLOWED = 117; // 0x75 field public static final int RESULT_RIL_RADIO_NOT_AVAILABLE = 100; // 0x64 field public static final int RESULT_RIL_REQUEST_NOT_SUPPORTED = 114; // 0x72 field public static final int RESULT_RIL_REQUEST_RATE_LIMITED = 106; // 0x6a field public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121; // 0x79 field public static final int RESULT_RIL_SIM_ABSENT = 120; // 0x78 + field public static final int RESULT_RIL_SIM_BUSY = 132; // 0x84 + field public static final int RESULT_RIL_SIM_ERROR = 129; // 0x81 + field public static final int RESULT_RIL_SIM_FULL = 133; // 0x85 + field public static final int RESULT_RIL_SIM_PIN2 = 126; // 0x7e + field public static final int RESULT_RIL_SIM_PUK2 = 127; // 0x7f field public static final int RESULT_RIL_SMS_SEND_FAIL_RETRY = 101; // 0x65 + field public static final int RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE = 128; // 0x80 field public static final int RESULT_RIL_SYSTEM_ERR = 108; // 0x6c field public static final int RESULT_SMS_BLOCKED_DURING_EMERGENCY = 29; // 0x1d field public static final int RESULT_SMS_SEND_RETRY_FAILED = 30; // 0x1e @@ -44048,6 +44092,7 @@ package android.telephony { field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5 + field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1 field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2 @@ -45563,6 +45608,14 @@ package android.text { field public static final int DONE = -1; // 0xffffffff } + public static class SegmentFinder.DefaultSegmentFinder extends android.text.SegmentFinder { + ctor public SegmentFinder.DefaultSegmentFinder(@NonNull int[]); + method public int nextEndBoundary(@IntRange(from=0) int); + method public int nextStartBoundary(@IntRange(from=0) int); + method public int previousEndBoundary(@IntRange(from=0) int); + method public int previousStartBoundary(@IntRange(from=0) int); + } + public class Selection { method public static boolean extendDown(android.text.Spannable, android.text.Layout); method public static boolean extendLeft(android.text.Spannable, android.text.Layout); @@ -48552,6 +48605,12 @@ package android.view { field public static final int VERTICAL_GRAVITY_MASK = 112; // 0x70 } + public class HandwritingDelegateConfiguration { + ctor public HandwritingDelegateConfiguration(@IdRes int, @NonNull Runnable); + method public int getDelegatorViewId(); + method @NonNull public Runnable getInitiationCallback(); + } + public class HapticFeedbackConstants { field public static final int CLOCK_TICK = 4; // 0x4 field public static final int CONFIRM = 16; // 0x10 @@ -49569,16 +49628,13 @@ package android.view { } public final class PixelCopy { - method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>); - method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>); - method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>); - method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>); method public static void request(@NonNull android.view.SurfaceView, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.SurfaceView, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.Surface, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.Surface, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.Window, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); method public static void request(@NonNull android.view.Window, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler); + method public static void request(@NonNull android.view.PixelCopy.Request); field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5 field public static final int ERROR_SOURCE_INVALID = 4; // 0x4 field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3 @@ -49587,19 +49643,28 @@ package android.view { field public static final int SUCCESS = 0; // 0x0 } - public static final class PixelCopy.CopyResult { - method @NonNull public android.graphics.Bitmap getBitmap(); - method public int getStatus(); - } - public static interface PixelCopy.OnPixelCopyFinishedListener { method public void onPixelCopyFinished(int); } public static final class PixelCopy.Request { - method public void request(); - method @NonNull public android.view.PixelCopy.Request setDestinationBitmap(@Nullable android.graphics.Bitmap); - method @NonNull public android.view.PixelCopy.Request setSourceRect(@Nullable android.graphics.Rect); + method @Nullable public android.graphics.Bitmap getDestinationBitmap(); + method @Nullable public android.graphics.Rect getSourceRect(); + method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>); + method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>); + method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>); + method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>); + } + + public static final class PixelCopy.Request.Builder { + method @NonNull public android.view.PixelCopy.Request build(); + method @NonNull public android.view.PixelCopy.Request.Builder setDestinationBitmap(@Nullable android.graphics.Bitmap); + method @NonNull public android.view.PixelCopy.Request.Builder setSourceRect(@Nullable android.graphics.Rect); + } + + public static final class PixelCopy.Result { + method @NonNull public android.graphics.Bitmap getBitmap(); + method public int getStatus(); } public final class PointerIcon implements android.os.Parcelable { @@ -50162,6 +50227,7 @@ package android.view { method public float getHandwritingBoundsOffsetLeft(); method public float getHandwritingBoundsOffsetRight(); method public float getHandwritingBoundsOffsetTop(); + method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration(); method public final boolean getHasOverlappingRendering(); method public final int getHeight(); method public void getHitRect(android.graphics.Rect); @@ -50528,6 +50594,7 @@ package android.view { method public void setForegroundTintList(@Nullable android.content.res.ColorStateList); method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode); method public void setHandwritingBoundsOffsets(float, float, float, float); + method public void setHandwritingDelegateConfiguration(@Nullable android.view.HandwritingDelegateConfiguration); method public void setHapticFeedbackEnabled(boolean); method public void setHasTransientState(boolean); method public void setHorizontalFadingEdgeEnabled(boolean); @@ -53307,6 +53374,7 @@ package android.view.inputmethod { method public android.graphics.Matrix getMatrix(); method public int getSelectionEnd(); method public int getSelectionStart(); + method @Nullable public android.view.inputmethod.TextAppearanceInfo getTextAppearanceInfo(); method @NonNull public java.util.List<android.graphics.RectF> getVisibleLineBounds(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.CursorAnchorInfo> CREATOR; @@ -53327,6 +53395,7 @@ package android.view.inputmethod { method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, int); method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix); method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int); + method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setTextAppearanceInfo(@Nullable android.view.inputmethod.TextAppearanceInfo); } public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable { @@ -53570,6 +53639,7 @@ package android.view.inputmethod { method public boolean reportFullscreenMode(boolean); method public boolean requestCursorUpdates(int); method public default boolean requestCursorUpdates(int, int); + method public default void requestTextBoundsInfo(@NonNull android.graphics.RectF, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inputmethod.TextBoundsInfoResult>); method public boolean sendKeyEvent(android.view.KeyEvent); method public boolean setComposingRegion(int, int); method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute); @@ -53581,6 +53651,7 @@ package android.view.inputmethod { field public static final int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 8; // 0x8 field public static final int CURSOR_UPDATE_FILTER_EDITOR_BOUNDS = 4; // 0x4 field public static final int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 16; // 0x10 + field public static final int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 64; // 0x40 field public static final int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 32; // 0x20 field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1 field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2 @@ -53884,6 +53955,35 @@ package android.view.inputmethod { field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR; } + public final class TextAppearanceInfo implements android.os.Parcelable { + ctor public TextAppearanceInfo(@NonNull android.widget.TextView); + method public int describeContents(); + method @Nullable public String getFontFamilyName(); + method @Nullable public String getFontFeatureSettings(); + method @Nullable public String getFontVariationSettings(); + method public float getLetterSpacing(); + method public int getLineBreakStyle(); + method public int getLineBreakWordStyle(); + method public int getMaxLength(); + method @Px public float getShadowDx(); + method @Px public float getShadowDy(); + method @Px public float getShadowRadius(); + method @ColorInt public int getTextColor(); + method @ColorInt public int getTextColorHighlight(); + method @ColorInt public int getTextColorHint(); + method @Nullable public android.content.res.ColorStateList getTextColorLink(); + method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight(); + method @NonNull public android.os.LocaleList getTextLocales(); + method public float getTextScaleX(); + method @Px public float getTextSize(); + method public int getTextStyle(); + method public boolean isAllCaps(); + method public boolean isElegantTextHeight(); + method public boolean isFallbackLineSpacing(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR; + } + public final class TextAttribute implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.os.PersistableBundle getExtras(); @@ -53899,6 +53999,50 @@ package android.view.inputmethod { method @NonNull public android.view.inputmethod.TextAttribute.Builder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>); } + public final class TextBoundsInfo implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0, to=125) public int getCharacterBidiLevel(int); + method @NonNull public android.graphics.RectF getCharacterBounds(int); + method public int getCharacterFlags(int); + method public int getEnd(); + method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder(); + method @NonNull public android.text.SegmentFinder getLineSegmentFinder(); + method @NonNull public android.graphics.Matrix getMatrix(); + method public int getStart(); + method @NonNull public android.text.SegmentFinder getWordSegmentFinder(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextBoundsInfo> CREATOR; + field public static final int FLAG_CHARACTER_LINEFEED = 2; // 0x2 + field public static final int FLAG_CHARACTER_PUNCTUATION = 4; // 0x4 + field public static final int FLAG_CHARACTER_WHITESPACE = 1; // 0x1 + field public static final int FLAG_LINE_IS_RTL = 8; // 0x8 + } + + public static final class TextBoundsInfo.Builder { + ctor public TextBoundsInfo.Builder(); + method @NonNull public android.view.inputmethod.TextBoundsInfo build(); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder clear(); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBidiLevel(@NonNull int[]); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBounds(@NonNull float[]); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterFlags(@NonNull int[]); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setGraphemeSegmentFinder(@NonNull android.text.SegmentFinder); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setLineSegmentFinder(@NonNull android.text.SegmentFinder); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setMatrix(@NonNull android.graphics.Matrix); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setStartAndEnd(@IntRange(from=0) int, @IntRange(from=0) int); + method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setWordSegmentFinder(@NonNull android.text.SegmentFinder); + } + + public final class TextBoundsInfoResult { + ctor public TextBoundsInfoResult(int); + ctor public TextBoundsInfoResult(int, @NonNull android.view.inputmethod.TextBoundsInfo); + method public int getResultCode(); + method @Nullable public android.view.inputmethod.TextBoundsInfo getTextBoundsInfo(); + field public static final int CODE_CANCELLED = 3; // 0x3 + field public static final int CODE_FAILED = 2; // 0x2 + field public static final int CODE_SUCCESS = 1; // 0x1 + field public static final int CODE_UNSUPPORTED = 0; // 0x0 + } + public final class TextSnapshot { ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int); method @IntRange(from=0xffffffff) public int getCompositionEnd(); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 88efcced78fb..ce18745046ff 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -340,10 +340,6 @@ package android.os { method public boolean shouldBypassCache(@NonNull Q); } - public interface Parcelable { - method public default int getStability(); - } - public class Process { method public static final int getAppUidForSdkSandboxUid(int); method public static final boolean isSdkSandboxUid(int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a382ecfc99d3..70b89b8af044 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -524,10 +524,12 @@ package android.app { } public class AlarmManager { - method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource); - method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, @NonNull android.app.PendingIntent, @Nullable android.os.WorkSource); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler, @Nullable android.os.WorkSource); method @RequiresPermission(allOf={android.Manifest.permission.UPDATE_DEVICE_STATS, android.Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional=true) public void setExact(int, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener); + method @RequiresPermission(allOf={android.Manifest.permission.UPDATE_DEVICE_STATS, android.Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional=true) public void setExactAndAllowWhileIdle(int, long, @Nullable String, @NonNull java.util.concurrent.Executor, @Nullable android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener); method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener); + method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void setWindow(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @Nullable android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener); } public class AppOpsManager { @@ -585,6 +587,7 @@ package android.app { field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio"; field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images"; field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video"; + field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected"; field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast"; field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio"; @@ -6343,12 +6346,21 @@ package android.media { public final class AudioPlaybackConfiguration implements android.os.Parcelable { method public int getClientPid(); method public int getClientUid(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy(); method public int getPlayerInterfaceId(); method public android.media.PlayerProxy getPlayerProxy(); method public int getPlayerState(); method public int getPlayerType(); method @IntRange(from=0) public int getSessionId(); method public boolean isActive(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted(); + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8 + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10 + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1 + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4 + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2 + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff + field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20 field public static final int PLAYER_STATE_IDLE = 1; // 0x1 field public static final int PLAYER_STATE_PAUSED = 3; // 0x3 field public static final int PLAYER_STATE_RELEASED = 0; // 0x0 @@ -9670,6 +9682,7 @@ package android.os { } public interface Parcelable { + method public default int getStability(); field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0 field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1 } @@ -9678,7 +9691,6 @@ package android.os { ctor public ParcelableHolder(int); method public int describeContents(); method @Nullable public <T extends android.os.Parcelable> T getParcelable(@NonNull Class<T>); - method public int getStability(); method public void readFromParcel(@NonNull android.os.Parcel); method public void setParcelable(@Nullable android.os.Parcelable); method public void writeToParcel(@NonNull android.os.Parcel, int); @@ -13704,6 +13716,7 @@ package android.telephony { field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED"; field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3 field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1 + field public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4; // 0x4 field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2 field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1 field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5 @@ -15921,10 +15934,17 @@ package android.view { package android.view.accessibility { + public abstract class AccessibilityDisplayProxy { + ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>); + method public int getDisplayId(); + } + public final class AccessibilityManager { method public int getAccessibilityWindowId(@Nullable android.os.IBinder); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut(); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean registerDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean unregisterDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy); method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index df07ee351bbc..e9f9136c768c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -420,7 +420,7 @@ package android.app { } public final class SyncNotedAppOp implements android.os.Parcelable { - ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String); + ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @Nullable String); } public class TaskInfo { diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java index c2c065b5a360..89601bc3e7f0 100644 --- a/core/java/android/accounts/AbstractAccountAuthenticator.java +++ b/core/java/android/accounts/AbstractAccountAuthenticator.java @@ -154,6 +154,8 @@ public abstract class AbstractAccountAuthenticator { public void addAccount(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options) throws RemoteException { + super.addAccount_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "addAccount: accountType " + accountType + ", authTokenType " + authTokenType @@ -184,6 +186,8 @@ public abstract class AbstractAccountAuthenticator { @Override public void confirmCredentials(IAccountAuthenticatorResponse response, Account account, Bundle options) throws RemoteException { + super.confirmCredentials_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "confirmCredentials: " + account); } @@ -210,6 +214,8 @@ public abstract class AbstractAccountAuthenticator { public void getAuthTokenLabel(IAccountAuthenticatorResponse response, String authTokenType) throws RemoteException { + super.getAuthTokenLabel_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType); } @@ -235,6 +241,8 @@ public abstract class AbstractAccountAuthenticator { public void getAuthToken(IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions) throws RemoteException { + super.getAuthToken_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAuthToken: " + account + ", authTokenType " + authTokenType); @@ -262,6 +270,8 @@ public abstract class AbstractAccountAuthenticator { @Override public void updateCredentials(IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions) throws RemoteException { + super.updateCredentials_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "updateCredentials: " + account + ", authTokenType " + authTokenType); @@ -291,6 +301,8 @@ public abstract class AbstractAccountAuthenticator { @Override public void editProperties(IAccountAuthenticatorResponse response, String accountType) throws RemoteException { + super.editProperties_enforcePermission(); + try { final Bundle result = AbstractAccountAuthenticator.this.editProperties( new AccountAuthenticatorResponse(response), accountType); @@ -306,6 +318,8 @@ public abstract class AbstractAccountAuthenticator { @Override public void hasFeatures(IAccountAuthenticatorResponse response, Account account, String[] features) throws RemoteException { + super.hasFeatures_enforcePermission(); + try { final Bundle result = AbstractAccountAuthenticator.this.hasFeatures( new AccountAuthenticatorResponse(response), account, features); @@ -321,6 +335,8 @@ public abstract class AbstractAccountAuthenticator { @Override public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response, Account account) throws RemoteException { + super.getAccountRemovalAllowed_enforcePermission(); + try { final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed( new AccountAuthenticatorResponse(response), account); @@ -336,6 +352,8 @@ public abstract class AbstractAccountAuthenticator { @Override public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, Account account) throws RemoteException { + super.getAccountCredentialsForCloning_enforcePermission(); + try { final Bundle result = AbstractAccountAuthenticator.this.getAccountCredentialsForCloning( @@ -353,6 +371,8 @@ public abstract class AbstractAccountAuthenticator { public void addAccountFromCredentials(IAccountAuthenticatorResponse response, Account account, Bundle accountCredentials) throws RemoteException { + super.addAccountFromCredentials_enforcePermission(); + try { final Bundle result = AbstractAccountAuthenticator.this.addAccountFromCredentials( @@ -371,6 +391,8 @@ public abstract class AbstractAccountAuthenticator { public void startAddAccountSession(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options) throws RemoteException { + super.startAddAccountSession_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "startAddAccountSession: accountType " + accountType @@ -403,6 +425,8 @@ public abstract class AbstractAccountAuthenticator { Account account, String authTokenType, Bundle loginOptions) throws RemoteException { + super.startUpdateCredentialsSession_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "startUpdateCredentialsSession: " + account @@ -441,6 +465,8 @@ public abstract class AbstractAccountAuthenticator { IAccountAuthenticatorResponse response, String accountType, Bundle sessionBundle) throws RemoteException { + super.finishSession_enforcePermission(); + if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "finishSession: accountType " + accountType); } @@ -468,6 +494,8 @@ public abstract class AbstractAccountAuthenticator { IAccountAuthenticatorResponse response, Account account, String statusToken) throws RemoteException { + super.isCredentialsUpdateSuggested_enforcePermission(); + try { final Bundle result = AbstractAccountAuthenticator.this .isCredentialsUpdateSuggested( diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index a573776e8670..b3db38d43efc 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -27,7 +27,6 @@ import android.annotation.Size; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.UserHandleAware; -import android.annotation.UserIdInt; import android.app.Activity; import android.app.PropertyInvalidatedCache; import android.compat.annotation.UnsupportedAppUsage; @@ -37,6 +36,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.UserPackage; import android.content.res.Resources; import android.database.SQLException; import android.os.Build; @@ -348,43 +348,11 @@ public class AccountManager { */ public static final int CACHE_ACCOUNTS_DATA_SIZE = 4; - private static final class UserIdPackage - { - @UserIdInt - public int userId; - public String packageName; - - public UserIdPackage(int UserId, String PackageName) { - this.userId = UserId; - this.packageName = PackageName; - } - - @Override - public boolean equals(@Nullable Object o) { - if (o == null) { - return false; - } - if (o == this) { - return true; - } - if (o.getClass() != getClass()) { - return false; - } - UserIdPackage e = (UserIdPackage) o; - return e.userId == userId && e.packageName.equals(packageName); - } - - @Override - public int hashCode() { - return userId ^ packageName.hashCode(); - } - } - - PropertyInvalidatedCache<UserIdPackage, Account[]> mAccountsForUserCache = - new PropertyInvalidatedCache<UserIdPackage, Account[]>( + PropertyInvalidatedCache<UserPackage, Account[]> mAccountsForUserCache = + new PropertyInvalidatedCache<UserPackage, Account[]>( CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) { @Override - public Account[] recompute(UserIdPackage userAndPackage) { + public Account[] recompute(UserPackage userAndPackage) { try { return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName); } catch (RemoteException e) { @@ -392,7 +360,7 @@ public class AccountManager { } } @Override - public boolean bypass(UserIdPackage query) { + public boolean bypass(UserPackage query) { return query.userId < 0; } @Override @@ -731,7 +699,7 @@ public class AccountManager { */ @NonNull public Account[] getAccountsAsUser(int userId) { - UserIdPackage userAndPackage = new UserIdPackage(userId, mContext.getOpPackageName()); + UserPackage userAndPackage = UserPackage.of(userId, mContext.getOpPackageName()); return mAccountsForUserCache.query(userAndPackage); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 32d0d75d5e41..501b136726d3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -8364,11 +8364,17 @@ public class Activity extends ContextThemeWrapper } final void performNewIntent(@NonNull Intent intent) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performNewIntent"); mCanEnterPictureInPicture = true; onNewIntent(intent); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void performStart(String reason) { + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:" + + mComponent.getClassName()); + } dispatchActivityPreStarted(); mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions()); mFragments.noteStateNotSaved(); @@ -8415,6 +8421,7 @@ public class Activity extends ContextThemeWrapper mActivityTransitionState.enterReady(this); dispatchActivityPostStarted(); + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } /** @@ -8423,7 +8430,8 @@ public class Activity extends ContextThemeWrapper * The option to not start immediately is needed in case a transaction with * multiple lifecycle transitions is in progress. */ - final void performRestart(boolean start, String reason) { + final void performRestart(boolean start) { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performRestart"); mCanEnterPictureInPicture = true; mFragments.noteStateNotSaved(); @@ -8458,17 +8466,18 @@ public class Activity extends ContextThemeWrapper final long startTime = SystemClock.uptimeMillis(); mInstrumentation.callActivityOnRestart(this); final long duration = SystemClock.uptimeMillis() - startTime; - EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason, - duration); + EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), + "performRestart", duration); if (!mCalled) { throw new SuperNotCalledException( "Activity " + mComponent.toShortString() + " did not call through to super.onRestart()"); } if (start) { - performStart(reason); + performStart("performRestart"); } } + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } final void performResume(boolean followedByPause, String reason) { @@ -8477,7 +8486,6 @@ public class Activity extends ContextThemeWrapper + mComponent.getClassName()); } dispatchActivityPreResumed(); - performRestart(true /* start */, reason); mFragments.execPendingActions(); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 4b1b0a260e27..baa3bd96343a 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -227,6 +227,12 @@ public abstract class ActivityManagerInternal { public abstract boolean isModernQueueEnabled(); /** + * Enforce capability restrictions on use of the given BroadcastOptions + */ + public abstract void enforceBroadcastOptionsPermissions(@Nullable Bundle options, + int callingUid); + + /** * Returns package name given pid. * * @param pid The pid we are searching package name for. @@ -300,7 +306,7 @@ public abstract class ActivityManagerInternal { public abstract int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId, boolean allowAll, int allowMode, String name, String callerPackage); - /** Checks if the calling binder pid as the permission. */ + /** Checks if the calling binder pid/uid has the given permission. */ @PermissionMethod public abstract void enforceCallingPermission(@PermissionName String permission, String func); @@ -389,6 +395,10 @@ public abstract class ActivityManagerInternal { */ public abstract boolean isAppStartModeDisabled(int uid, String packageName); + /** + * Returns the ids of the current user and all of its profiles (if any), regardless of the + * running state of the profiles. + */ public abstract int[] getCurrentProfileIds(); public abstract UserInfo getCurrentUser(); public abstract void ensureNotSpecialUser(@UserIdInt int userId); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 67d441611e63..1f6334381bc9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -173,7 +173,6 @@ import android.util.UtilConfig; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; import android.view.Display; -import android.view.DisplayAdjustments; import android.view.SurfaceControl; import android.view.ThreadedRenderer; import android.view.View; @@ -244,7 +243,6 @@ import java.util.Objects; import java.util.TimeZone; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; /** * This manages the execution of the main thread in an @@ -409,7 +407,6 @@ public final class ActivityThread extends ClientTransactionHandler boolean mInstrumentingWithoutRestart; boolean mSystemThread = false; boolean mSomeActivitiesChanged = false; - /* package */ boolean mHiddenApiWarningShown = false; // These can be accessed by multiple threads; mResourcesManager is the lock. // XXX For now we keep around information about all packages we have @@ -438,16 +435,10 @@ public final class ActivityThread extends ClientTransactionHandler @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final ResourcesManager mResourcesManager; - /** The active adjustments that override the {@link DisplayAdjustments} in resources. */ - private ArrayList<Pair<IBinder, Consumer<DisplayAdjustments>>> mActiveRotationAdjustments; - // Registry of remote cancellation transports pending a reply with reply handles. @GuardedBy("this") private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations; - private final Map<IBinder, Integer> mLastReportedWindowingMode = Collections.synchronizedMap( - new ArrayMap<>()); - private static final class ProviderKey { final String authority; final int userId; @@ -518,8 +509,6 @@ public final class ActivityThread extends ClientTransactionHandler */ private final Object mCoreSettingsLock = new Object(); - boolean mHasImeComponent = false; - private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null; /** A client side controller to handle process level configuration changes. */ @@ -599,6 +588,12 @@ public final class ActivityThread extends ClientTransactionHandler /** Whether this activiy was launched from a bubble. */ boolean mLaunchedFromBubble; + /** + * This can be different from the current configuration because a new configuration may not + * always update to activity, e.g. windowing mode change without size change. + */ + int mLastReportedWindowingMode = WINDOWING_MODE_UNDEFINED; + @LifecycleState private int mLifecycleState = PRE_ON_CREATE; @@ -3654,8 +3649,7 @@ public final class ActivityThread extends ClientTransactionHandler "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } - mLastReportedWindowingMode.put(activity.getActivityToken(), - config.windowConfiguration.getWindowingMode()); + r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode(); } r.setState(ON_CREATE); @@ -3714,12 +3708,14 @@ public final class ActivityThread extends ClientTransactionHandler // Call postOnCreate() if (pendingActions.shouldCallOnPostCreate()) { activity.mCalled = false; + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onPostCreate"); if (r.isPersistable()) { mInstrumentation.callActivityOnPostCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnPostCreate(activity, r.state); } + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() @@ -5268,7 +5264,7 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void performRestartActivity(ActivityClientRecord r, boolean start) { if (r.stopped) { - r.activity.performRestart(start, "performRestartActivity"); + r.activity.performRestart(start); if (start) { r.setState(ON_START); } @@ -5285,7 +5281,7 @@ public final class ActivityThread extends ClientTransactionHandler private void onCoreSettingsChange() { if (updateDebugViewAttributeState()) { // request all activities to relaunch for the changes to take place - relaunchAllActivities(false /* preserveWindows */, "onCoreSettingsChange"); + relaunchAllActivities(true /* preserveWindows */, "onCoreSettingsChange"); } } @@ -5433,7 +5429,6 @@ public final class ActivityThread extends ClientTransactionHandler } } r.setState(ON_DESTROY); - mLastReportedWindowingMode.remove(r.activity.getActivityToken()); schedulePurgeIdler(); synchronized (this) { if (mSplashScreenGlobal != null) { @@ -5865,7 +5860,7 @@ public final class ActivityThread extends ClientTransactionHandler if (r.overrideConfig != null) { r.tmpConfig.updateFrom(r.overrideConfig); } - final Configuration reportedConfig = performActivityConfigurationChanged(r.activity, + final Configuration reportedConfig = performActivityConfigurationChanged(r, r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig)); return reportedConfig; @@ -5873,7 +5868,7 @@ public final class ActivityThread extends ClientTransactionHandler /** * Decides whether to update an Activity's configuration and whether to inform it. - * @param activity The activity to notify of configuration change. + * @param r The activity client record to notify of configuration change. * @param newConfig The new configuration. * @param amOverrideConfig The override config that differentiates the Activity's configuration * from the base global configuration. This is supplied by @@ -5881,29 +5876,29 @@ public final class ActivityThread extends ClientTransactionHandler * @param displayId Id of the display where activity currently resides. * @return Configuration sent to client, null if no changes and not moved to different display. */ - private Configuration performActivityConfigurationChanged(Activity activity, + private Configuration performActivityConfigurationChanged(ActivityClientRecord r, Configuration newConfig, Configuration amOverrideConfig, int displayId, boolean alwaysReportChange) { + final Activity activity = r.activity; final IBinder activityToken = activity.getActivityToken(); // WindowConfiguration differences aren't considered as public, check it separately. // multi-window / pip mode changes, if any, should be sent before the configuration // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition - handleWindowingModeChangeIfNeeded(activity, newConfig); + handleWindowingModeChangeIfNeeded(r, newConfig); final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(), displayId); final Configuration currentResConfig = activity.getResources().getConfiguration(); final int diff = currentResConfig.diffPublicOnly(newConfig); final boolean hasPublicResConfigChange = diff != 0; - final ActivityClientRecord r = getActivityClient(activityToken); // TODO(b/173090263): Use diff instead after the improvement of AssetManager and // ResourcesImpl constructions. final boolean shouldUpdateResources = hasPublicResConfigChange || shouldUpdateResources(activityToken, currentResConfig, newConfig, amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange); final boolean shouldReportChange = shouldReportChange( - activity.mCurrentConfig, newConfig, r != null ? r.mSizeConfigurations : null, + activity.mCurrentConfig, newConfig, r.mSizeConfigurations, activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange); // Nothing significant, don't proceed with updating and reporting. if (!shouldUpdateResources && !shouldReportChange) { @@ -6012,12 +6007,11 @@ public final class ActivityThread extends ClientTransactionHandler * See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and * {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)} */ - private void handleWindowingModeChangeIfNeeded(Activity activity, + private void handleWindowingModeChangeIfNeeded(ActivityClientRecord r, Configuration newConfiguration) { + final Activity activity = r.activity; final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode(); - final IBinder token = activity.getActivityToken(); - final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(token, - WINDOWING_MODE_UNDEFINED); + final int oldWindowingMode = r.mLastReportedWindowingMode; if (oldWindowingMode == newWindowingMode) return; // PiP callback is sent before the MW one. if (newWindowingMode == WINDOWING_MODE_PINNED) { @@ -6032,7 +6026,7 @@ public final class ActivityThread extends ClientTransactionHandler if (wasInMultiWindowMode != nowInMultiWindowMode) { activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration); } - mLastReportedWindowingMode.put(token, newWindowingMode); + r.mLastReportedWindowingMode = newWindowingMode; } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1b972e0cb81a..267e5b699241 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1360,9 +1360,17 @@ public class AppOpsManager { */ public static final int OP_RUN_LONG_JOBS = AppProtoEnums.APP_OP_RUN_LONG_JOBS; + /** + * Notify apps that they have been granted URI permission photos + * + * @hide + */ + public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED = + AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 123; + public static final int _NUM_OP = 124; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1833,6 +1841,14 @@ public class AppOpsManager { @SystemApi public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; + /** + * Notify apps that they have been granted URI permission photos + * + * @hide + */ + @SystemApi + public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = + "android:read_media_visual_user_selected"; /** * Record audio from near-field microphone (ie. TV remote) @@ -1948,6 +1964,7 @@ public class AppOpsManager { OP_MANAGE_MEDIA, OP_TURN_SCREEN_ON, OP_RUN_LONG_JOBS, + OP_READ_MEDIA_VISUAL_USER_SELECTED, }; static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{ @@ -2329,7 +2346,11 @@ public class AppOpsManager { "RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode( AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS") - .setPermission(Manifest.permission.RUN_LONG_JOBS).build() + .setPermission(Manifest.permission.RUN_LONG_JOBS).build(), + new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED, + OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED") + .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) + .setDefaultMode(AppOpsManager.MODE_ALLOWED).build() }; /** diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index 48638d1fdff4..45d44589b2d8 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -636,6 +636,7 @@ public class BroadcastOptions extends ComponentOptions { * @param broadcastIsInteractive * @see #isInteractiveBroadcast() */ + @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE) public void setInteractiveBroadcast(boolean broadcastIsInteractive) { mIsInteractiveBroadcast = broadcastIsInteractive; } diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java index f156b30d5050..f674e882d34e 100644 --- a/core/java/android/app/SyncNotedAppOp.java +++ b/core/java/android/app/SyncNotedAppOp.java @@ -56,7 +56,7 @@ public final class SyncNotedAppOp implements Parcelable { * The package this op applies to * @hide */ - private final @NonNull String mPackageName; + private final @Nullable String mPackageName; /** * Native code relies on parcel ordering, do not change @@ -64,7 +64,7 @@ public final class SyncNotedAppOp implements Parcelable { */ @TestApi public SyncNotedAppOp(int opMode, @IntRange(from = 0L) int opCode, - @Nullable String attributionTag, @NonNull String packageName) { + @Nullable String attributionTag, @Nullable String packageName) { this.mOpCode = opCode; com.android.internal.util.AnnotationValidations.validate( IntRange.class, null, mOpCode, @@ -101,7 +101,7 @@ public final class SyncNotedAppOp implements Parcelable { * @hide */ public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag, - @NonNull String packageName) { + @Nullable String packageName) { this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, packageName); } @@ -152,7 +152,7 @@ public final class SyncNotedAppOp implements Parcelable { * @hide */ @DataClass.Generated.Member - public @NonNull String getPackageName() { + public @Nullable String getPackageName() { return mPackageName; } @@ -211,11 +211,12 @@ public final class SyncNotedAppOp implements Parcelable { byte flg = 0; if (mAttributionTag != null) flg |= 0x4; + if (mPackageName != null) flg |= 0x8; dest.writeByte(flg); dest.writeInt(mOpMode); dest.writeInt(mOpCode); if (mAttributionTag != null) dest.writeString(mAttributionTag); - dest.writeString(mPackageName); + if (mPackageName != null) dest.writeString(mPackageName); } @Override @@ -233,7 +234,7 @@ public final class SyncNotedAppOp implements Parcelable { int opMode = in.readInt(); int opCode = in.readInt(); String attributionTag = (flg & 0x4) == 0 ? null : in.readString(); - String packageName = in.readString(); + String packageName = (flg & 0x8) == 0 ? null : in.readString(); this.mOpMode = opMode; this.mOpCode = opCode; @@ -243,8 +244,6 @@ public final class SyncNotedAppOp implements Parcelable { "to", AppOpsManager._NUM_OP - 1); this.mAttributionTag = attributionTag; this.mPackageName = packageName; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mPackageName); // onConstructed(); // You can define this method to get a callback } @@ -264,10 +263,10 @@ public final class SyncNotedAppOp implements Parcelable { }; @DataClass.Generated( - time = 1643320427700L, + time = 1667247337573L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java", - inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)") + inputSignatures = "private final int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.Nullable java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic int getOpMode()\nprivate java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 0f26818ead71..ef10c0b245d1 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -115,6 +115,15 @@ "file_patterns": ["(/|^)VoiceInteract[^/]*"] }, { + "name": "CtsLocalVoiceInteraction", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ], + "file_patterns": ["(/|^)VoiceInteract[^/]*"] + }, + { "name": "CtsOsTestCases", "options": [ { diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index a2dc47d44cb9..5a2f2616dc13 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -458,7 +458,8 @@ public class TaskInfo { && isVisible == that.isVisible && isSleeping == that.isSleeping && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId) - && parentTaskId == that.parentTaskId; + && parentTaskId == that.parentTaskId + && Objects.equals(topActivity, that.topActivity); } /** diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 95e9c22373de..5e15b0a19a6a 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -2456,7 +2456,12 @@ public class WallpaperManager { public void waitForCompletion() { try { - mLatch.await(30, TimeUnit.SECONDS); + final boolean completed = mLatch.await(30, TimeUnit.SECONDS); + if (completed) { + Log.d(TAG, "Wallpaper set completion."); + } else { + Log.d(TAG, "Timeout waiting for wallpaper set completion!"); + } } catch (InterruptedException e) { // This might be legit: the crop may take a very long time. Don't sweat // it in that case; we are okay with display lagging behind in order to diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index de196870ef4e..f4cee5a09ff4 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -3864,6 +3864,56 @@ public class DevicePolicyManager { public static final String EXTRA_RESOURCE_IDS = "android.app.extra.RESOURCE_IDS"; + /** Allow the user to choose whether to enable MTE on the device. */ + public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0; + + /** + * Require that MTE be enabled on the device, if supported. Can be set by a device owner or a + * profile owner of an organization-owned managed profile. + */ + public static final int MTE_ENABLED = 1; + + /** Require that MTE be disabled on the device. Can be set by a device owner. */ + public static final int MTE_DISABLED = 2; + + /** @hide */ + @IntDef( + prefix = {"MTE_"}, + value = {MTE_ENABLED, MTE_DISABLED, MTE_NOT_CONTROLLED_BY_POLICY}) + @Retention(RetentionPolicy.SOURCE) + public static @interface MtePolicy {} + + /** + * Set MTE policy for device. MTE_ENABLED does not necessarily enable MTE if set on a device + * that does not support MTE. + * + * The default policy is MTE_NOT_CONTROLLED_BY_POLICY. + * + * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain + * classes of security problems at a small runtime performance cost overhead. + * + * @param policy the policy to be set + */ + public void setMtePolicy(@MtePolicy int policy) { + // TODO(b/244290023): implement + // This is SecurityException to temporarily make ParentProfileTest happy. + // This is not used. + throw new SecurityException("not implemented"); + } + + /** + * Get currently set MTE policy. This is not necessarily the same as the state of MTE on the + * device, as the device might not support MTE. + * + * @return the currently set policy + */ + public @MtePolicy int getMtePolicy() { + // TODO(b/244290023): implement + // This is SecurityException to temporarily make ParentProfileTest happy. + // This is not used. + throw new SecurityException("not implemented"); + } + /** * This object is a single place to tack on invalidation and disable calls. All * binder caches in this class derive from this Config, so all can be invalidated or diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java index 3758cdb680b1..a8336615fde5 100644 --- a/core/java/android/companion/AssociatedDevice.java +++ b/core/java/android/companion/AssociatedDevice.java @@ -16,6 +16,7 @@ package android.companion; +import android.bluetooth.BluetoothDevice; import android.os.Parcel; import android.os.Parcelable; @@ -23,19 +24,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** - * Loose wrapper around device parcelable. Device can be one of three types: + * Container for device info from an association that is not self-managed. + * Device can be one of three types: * * <ul> * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li> * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> * </ul> - * - * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from - * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app, - * and should only be used internally. - * - * @hide */ public final class AssociatedDevice implements Parcelable { private static final int CLASSIC_BLUETOOTH = 0; @@ -44,6 +40,7 @@ public final class AssociatedDevice implements Parcelable { @NonNull private final Parcelable mDevice; + /** @hide */ public AssociatedDevice(@NonNull Parcelable device) { mDevice = device; } @@ -54,11 +51,39 @@ public final class AssociatedDevice implements Parcelable { } /** - * Return device info. Cast to expected device type. + * Return bluetooth device info. Null if associated device is not a bluetooth device. + * @return Remote bluetooth device details containing MAC address. */ - @NonNull - public Parcelable getDevice() { - return mDevice; + @Nullable + public BluetoothDevice getBluetoothDevice() { + if (mDevice instanceof BluetoothDevice) { + return (BluetoothDevice) mDevice; + } + return null; + } + + /** + * Return bluetooth LE device info. Null if associated device is not a BLE device. + * @return BLE scan result containing details of detected BLE device. + */ + @Nullable + public android.bluetooth.le.ScanResult getBleDevice() { + if (mDevice instanceof android.bluetooth.le.ScanResult) { + return (android.bluetooth.le.ScanResult) mDevice; + } + return null; + } + + /** + * Return Wi-Fi device info. Null if associated device is not a Wi-Fi device. + * @return Wi-Fi scan result containing details of detected access point. + */ + @Nullable + public android.net.wifi.ScanResult getWifiDevice() { + if (mDevice instanceof android.net.wifi.ScanResult) { + return (android.net.wifi.ScanResult) mDevice; + } + return null; } @Override diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 93964b3f4180..5fd39feceb23 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -164,20 +164,19 @@ public final class AssociationInfo implements Parcelable { /** * Companion device that was associated. Note that this field is not persisted across sessions. - * - * Cast to expected device type before use: + * Device can be one of the following types: * * <ul> - * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li> - * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> - * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> + * <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li> + * <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li> + * <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li> * </ul> * * @return the companion device that was associated, or {@code null} if the device is - * self-managed. + * self-managed or this association info was retrieved from persistent storage. */ - public @Nullable Parcelable getAssociatedDevice() { - return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice(); + public @Nullable AssociatedDevice getAssociatedDevice() { + return mAssociatedDevice; } /** diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index d2fb1fb7fdf4..d7686e22756e 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -3346,7 +3346,7 @@ public class PackageInstaller { /** * The label representing the app to be installed. */ - private final @NonNull String mLabel; + private final @NonNull CharSequence mLabel; /** * The locale of the app label being used. */ @@ -3388,7 +3388,7 @@ public class PackageInstaller { @DataClass.Generated.Member public PreapprovalDetails( @Nullable Bitmap icon, - @NonNull String label, + @NonNull CharSequence label, @NonNull ULocale locale, @NonNull String packageName) { this.mIcon = icon; @@ -3417,7 +3417,7 @@ public class PackageInstaller { * The label representing the app to be installed. */ @DataClass.Generated.Member - public @NonNull String getLabel() { + public @NonNull CharSequence getLabel() { return mLabel; } @@ -3461,7 +3461,7 @@ public class PackageInstaller { if (mIcon != null) flg |= 0x1; dest.writeByte(flg); if (mIcon != null) mIcon.writeToParcel(dest, flags); - dest.writeString8(mLabel); + dest.writeCharSequence(mLabel); dest.writeString8(mLocale.toString()); dest.writeString8(mPackageName); } @@ -3479,7 +3479,7 @@ public class PackageInstaller { byte flg = in.readByte(); Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in); - String label = in.readString8(); + CharSequence label = (CharSequence) in.readCharSequence(); ULocale locale = new ULocale(in.readString8()); String packageName = in.readString8(); @@ -3519,7 +3519,7 @@ public class PackageInstaller { public static final class Builder { private @Nullable Bitmap mIcon; - private @NonNull String mLabel; + private @NonNull CharSequence mLabel; private @NonNull ULocale mLocale; private @NonNull String mPackageName; @@ -3545,7 +3545,7 @@ public class PackageInstaller { * The label representing the app to be installed. */ @DataClass.Generated.Member - public @NonNull Builder setLabel(@NonNull String value) { + public @NonNull Builder setLabel(@NonNull CharSequence value) { checkNotUsed(); mBuilderFieldsSet |= 0x2; mLabel = value; @@ -3596,10 +3596,10 @@ public class PackageInstaller { } @DataClass.Generated( - time = 1664257135109L, + time = 1666748098353L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java", - inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.String mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)") + inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java new file mode 100644 index 000000000000..e75f55174b4c --- /dev/null +++ b/core/java/android/content/pm/UserPackage.java @@ -0,0 +1,125 @@ +/* + * 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.content.pm; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.os.Process; +import android.os.UserHandle; +import android.util.SparseArrayMap; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.util.Objects; + +/** + * POJO to represent a package for a specific user ID. + * + * @hide + */ +public final class UserPackage { + @UserIdInt + public final int userId; + public final String packageName; + + @GuardedBy("sCache") + private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>(); + + private static final Object sUserIdLock = new Object(); + private static final class NoPreloadHolder { + /** Set of userIDs to cache objects for. */ + @GuardedBy("sUserIdLock") + private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())}; + } + + private UserPackage(int userId, String packageName) { + this.userId = userId; + this.packageName = packageName; + } + + @Override + public String toString() { + return "<" + userId + ">" + packageName; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof UserPackage) { + UserPackage other = (UserPackage) obj; + return userId == other.userId && Objects.equals(packageName, other.packageName); + } + return false; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + userId; + result = 31 * result + packageName.hashCode(); + return result; + } + + /** Return an instance of this class representing the given userId + packageName combination. */ + @NonNull + public static UserPackage of(@UserIdInt int userId, @NonNull String packageName) { + synchronized (sUserIdLock) { + if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) { + // Don't cache objects for invalid userIds. + return new UserPackage(userId, packageName); + } + } + synchronized (sCache) { + UserPackage up = sCache.get(userId, packageName); + if (up == null) { + packageName = packageName.intern(); + up = new UserPackage(userId, packageName); + sCache.add(userId, packageName, up); + } + return up; + } + } + + /** Remove the specified app from the cache. */ + public static void removeFromCache(@UserIdInt int userId, @NonNull String packageName) { + synchronized (sCache) { + sCache.delete(userId, packageName); + } + } + + /** Indicate the list of valid user IDs on the device. */ + public static void setValidUserIds(@NonNull int[] userIds) { + userIds = userIds.clone(); + synchronized (sUserIdLock) { + NoPreloadHolder.sUserIds = userIds; + } + synchronized (sCache) { + for (int u = sCache.numMaps() - 1; u >= 0; --u) { + final int userId = sCache.keyAt(u); + // Not holding sUserIdLock is intentional here. We don't modify the elements within + // the array and so even if this method is called multiple times with different sets + // of user IDs, we want to adjust the cache based on each new array. + if (!ArrayUtils.contains(userIds, userId)) { + sCache.deleteAt(u); + } + } + } + } +} diff --git a/core/java/android/credentials/Credential.java b/core/java/android/credentials/Credential.java index fed259254239..db89170b0cd7 100644 --- a/core/java/android/credentials/Credential.java +++ b/core/java/android/credentials/Credential.java @@ -36,7 +36,8 @@ public final class Credential implements Parcelable { * * @hide */ - @NonNull public static final String TYPE_PASSWORD = "android.credentials.TYPE_PASSWORD"; + @NonNull public static final String TYPE_PASSWORD_CREDENTIAL = + "android.credentials.TYPE_PASSWORD_CREDENTIAL"; /** * The credential type. diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java new file mode 100644 index 000000000000..9cc9c7289f9b --- /dev/null +++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java @@ -0,0 +1,164 @@ +/* + * 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.credentials.ui; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Per-provider metadata and entries for the create-credential flow. + * + * @hide + */ +public class CreateCredentialProviderData extends ProviderData implements Parcelable { + @NonNull + private final List<Entry> mSaveEntries; + @NonNull + private final List<Entry> mActionChips; + private final boolean mIsDefaultProvider; + @Nullable + private final Entry mRemoteEntry; + + public CreateCredentialProviderData( + @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries, + @NonNull List<Entry> actionChips, boolean isDefaultProvider, + @Nullable Entry remoteEntry) { + super(providerFlattenedComponentName); + mSaveEntries = saveEntries; + mActionChips = actionChips; + mIsDefaultProvider = isDefaultProvider; + mRemoteEntry = remoteEntry; + } + + @NonNull + public List<Entry> getSaveEntries() { + return mSaveEntries; + } + + @NonNull + public List<Entry> getActionChips() { + return mActionChips; + } + + public boolean isDefaultProvider() { + return mIsDefaultProvider; + } + + @Nullable + public Entry getRemoteEntry() { + return mRemoteEntry; + } + + protected CreateCredentialProviderData(@NonNull Parcel in) { + super(in); + + List<Entry> credentialEntries = new ArrayList<>(); + in.readTypedList(credentialEntries, Entry.CREATOR); + mSaveEntries = credentialEntries; + AnnotationValidations.validate(NonNull.class, null, mSaveEntries); + + List<Entry> actionChips = new ArrayList<>(); + in.readTypedList(actionChips, Entry.CREATOR); + mActionChips = actionChips; + AnnotationValidations.validate(NonNull.class, null, mActionChips); + + mIsDefaultProvider = in.readBoolean(); + + Entry remoteEntry = in.readTypedObject(Entry.CREATOR); + mRemoteEntry = remoteEntry; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeTypedList(mSaveEntries); + dest.writeTypedList(mActionChips); + dest.writeBoolean(isDefaultProvider()); + dest.writeTypedObject(mRemoteEntry, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<CreateCredentialProviderData> CREATOR = + new Creator<CreateCredentialProviderData>() { + @Override + public CreateCredentialProviderData createFromParcel(@NonNull Parcel in) { + return new CreateCredentialProviderData(in); + } + + @Override + public CreateCredentialProviderData[] newArray(int size) { + return new CreateCredentialProviderData[size]; + } + }; + + /** + * Builder for {@link CreateCredentialProviderData}. + * + * @hide + */ + public static class Builder { + private @NonNull String mProviderFlattenedComponentName; + private @NonNull List<Entry> mSaveEntries = new ArrayList<>(); + private @NonNull List<Entry> mActionChips = new ArrayList<>(); + private boolean mIsDefaultProvider = false; + private @Nullable Entry mRemoteEntry = null; + + /** Constructor with required properties. */ + public Builder(@NonNull String providerFlattenedComponentName) { + mProviderFlattenedComponentName = providerFlattenedComponentName; + } + + /** Sets the list of save credential entries to be displayed to the user. */ + @NonNull + public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) { + mSaveEntries = credentialEntries; + return this; + } + + /** Sets the list of action chips to be displayed to the user. */ + @NonNull + public Builder setActionChips(@NonNull List<Entry> actionChips) { + mActionChips = actionChips; + return this; + } + + /** Sets whether this provider is the user's selected default provider. */ + @NonNull + public Builder setIsDefaultProvider(boolean isDefaultProvider) { + mIsDefaultProvider = isDefaultProvider; + return this; + } + + /** Builds a {@link CreateCredentialProviderData}. */ + @NonNull + public CreateCredentialProviderData build() { + return new CreateCredentialProviderData(mProviderFlattenedComponentName, + mSaveEntries, mActionChips, mIsDefaultProvider, mRemoteEntry); + } + } +} diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java new file mode 100644 index 000000000000..73c8dbe427a7 --- /dev/null +++ b/core/java/android/credentials/ui/DisabledProviderData.java @@ -0,0 +1,60 @@ +/* + * 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.credentials.ui; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Metadata of a disabled provider. + * + * @hide + */ +public class DisabledProviderData extends ProviderData implements Parcelable { + + public DisabledProviderData( + @NonNull String providerFlattenedComponentName) { + super(providerFlattenedComponentName); + } + + protected DisabledProviderData(@NonNull Parcel in) { + super(in); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() { + @Override + public DisabledProviderData createFromParcel(@NonNull Parcel in) { + return new DisabledProviderData(in); + } + + @Override + public DisabledProviderData[] newArray(int size) { + return new DisabledProviderData[size]; + } + }; +} diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java new file mode 100644 index 000000000000..834f9825208f --- /dev/null +++ b/core/java/android/credentials/ui/GetCredentialProviderData.java @@ -0,0 +1,174 @@ +/* + * 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.credentials.ui; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Per-provider metadata and entries for the get-credential flow. + * + * @hide + */ +public class GetCredentialProviderData extends ProviderData implements Parcelable { + @NonNull + private final List<Entry> mCredentialEntries; + @NonNull + private final List<Entry> mActionChips; + @Nullable + private final Entry mAuthenticationEntry; + @Nullable + private final Entry mRemoteEntry; + + public GetCredentialProviderData( + @NonNull String providerFlattenedComponentName, @NonNull List<Entry> credentialEntries, + @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry, + @Nullable Entry remoteEntry) { + super(providerFlattenedComponentName); + mCredentialEntries = credentialEntries; + mActionChips = actionChips; + mAuthenticationEntry = authenticationEntry; + mRemoteEntry = remoteEntry; + } + + @NonNull + public List<Entry> getCredentialEntries() { + return mCredentialEntries; + } + + @NonNull + public List<Entry> getActionChips() { + return mActionChips; + } + + @Nullable + public Entry getAuthenticationEntry() { + return mAuthenticationEntry; + } + + @Nullable + public Entry getRemoteEntry() { + return mRemoteEntry; + } + + protected GetCredentialProviderData(@NonNull Parcel in) { + super(in); + + List<Entry> credentialEntries = new ArrayList<>(); + in.readTypedList(credentialEntries, Entry.CREATOR); + mCredentialEntries = credentialEntries; + AnnotationValidations.validate(NonNull.class, null, mCredentialEntries); + + List<Entry> actionChips = new ArrayList<>(); + in.readTypedList(actionChips, Entry.CREATOR); + mActionChips = actionChips; + AnnotationValidations.validate(NonNull.class, null, mActionChips); + + Entry authenticationEntry = in.readTypedObject(Entry.CREATOR); + mAuthenticationEntry = authenticationEntry; + + Entry remoteEntry = in.readTypedObject(Entry.CREATOR); + mRemoteEntry = remoteEntry; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeTypedList(mCredentialEntries); + dest.writeTypedList(mActionChips); + dest.writeTypedObject(mAuthenticationEntry, flags); + dest.writeTypedObject(mRemoteEntry, flags); + } + + @Override + public int describeContents() { + return 0; + } + + public static final @NonNull Creator<GetCredentialProviderData> CREATOR = + new Creator<GetCredentialProviderData>() { + @Override + public GetCredentialProviderData createFromParcel(@NonNull Parcel in) { + return new GetCredentialProviderData(in); + } + + @Override + public GetCredentialProviderData[] newArray(int size) { + return new GetCredentialProviderData[size]; + } + }; + + /** + * Builder for {@link GetCredentialProviderData}. + * + * @hide + */ + public static class Builder { + private @NonNull String mProviderFlattenedComponentName; + private @NonNull List<Entry> mCredentialEntries = new ArrayList<>(); + private @NonNull List<Entry> mActionChips = new ArrayList<>(); + private @Nullable Entry mAuthenticationEntry = null; + private @Nullable Entry mRemoteEntry = null; + + /** Constructor with required properties. */ + public Builder(@NonNull String providerFlattenedComponentName) { + mProviderFlattenedComponentName = providerFlattenedComponentName; + } + + /** Sets the list of save / get credential entries to be displayed to the user. */ + @NonNull + public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) { + mCredentialEntries = credentialEntries; + return this; + } + + /** Sets the list of action chips to be displayed to the user. */ + @NonNull + public Builder setActionChips(@NonNull List<Entry> actionChips) { + mActionChips = actionChips; + return this; + } + + /** Sets the authentication entry to be displayed to the user. */ + @NonNull + public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) { + mAuthenticationEntry = authenticationEntry; + return this; + } + + /** Sets the remote entry to be displayed to the user. */ + @NonNull + public Builder setRemoteEntry(@Nullable Entry remoteEntry) { + mRemoteEntry = remoteEntry; + return this; + } + + /** Builds a {@link GetCredentialProviderData}. */ + @NonNull + public GetCredentialProviderData build() { + return new GetCredentialProviderData(mProviderFlattenedComponentName, + mCredentialEntries, mActionChips, mAuthenticationEntry, mRemoteEntry); + } + } +} diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java index 1b70ea4ebd71..475169670eb1 100644 --- a/core/java/android/credentials/ui/IntentFactory.java +++ b/core/java/android/credentials/ui/IntentFactory.java @@ -30,15 +30,20 @@ import java.util.ArrayList; */ public class IntentFactory { /** Generate a new launch intent to the . */ - public static Intent newIntent(RequestInfo requestInfo, - ArrayList<ProviderData> providerDataList, ResultReceiver resultReceiver) { + public static Intent newIntent( + RequestInfo requestInfo, + ArrayList<ProviderData> enabledProviderDataList, + ArrayList<DisabledProviderData> disabledProviderDataList, + ResultReceiver resultReceiver) { Intent intent = new Intent(); // TODO: define these as proper config strings. String activityName = "com.android.credentialmanager/.CredentialSelectorActivity"; intent.setComponent(ComponentName.unflattenFromString(activityName)); intent.putParcelableArrayListExtra( - ProviderData.EXTRA_PROVIDER_DATA_LIST, providerDataList); + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList); + intent.putParcelableArrayListExtra( + ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList); intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo); intent.putExtra(Constants.EXTRA_RESULT_RECEIVER, toIpcFriendlyResultReceiver(resultReceiver)); diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java index 3728469d723d..eeaeb46e7896 100644 --- a/core/java/android/credentials/ui/ProviderData.java +++ b/core/java/android/credentials/ui/ProviderData.java @@ -16,232 +16,62 @@ package android.credentials.ui; -import android.annotation.CurrentTimeMillisLong; import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.drawable.Icon; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.AnnotationValidations; -import java.util.ArrayList; -import java.util.List; - /** - * Holds metadata and credential entries for a single provider. + * Super class for data structures that hold metadata and credential entries for a single provider. * * @hide */ -public class ProviderData implements Parcelable { +public abstract class ProviderData implements Parcelable { /** - * The intent extra key for the list of {@code ProviderData} when launching the UX - * activities. + * The intent extra key for the list of {@code ProviderData} from active providers when + * launching the UX activities. + */ + public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST = + "android.credentials.ui.extra.ENABLED_PROVIDER_DATA_LIST"; + /** + * The intent extra key for the list of {@code ProviderData} from disabled providers when + * launching the UX activities. */ - public static final String EXTRA_PROVIDER_DATA_LIST = - "android.credentials.ui.extra.PROVIDER_DATA_LIST"; + public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST = + "android.credentials.ui.extra.DISABLED_PROVIDER_DATA_LIST"; @NonNull private final String mProviderFlattenedComponentName; - @NonNull - private final String mProviderDisplayName; - @Nullable - private final Icon mIcon; - @NonNull - private final List<Entry> mCredentialEntries; - @NonNull - private final List<Entry> mActionChips; - @Nullable - private final Entry mAuthenticationEntry; - - private final @CurrentTimeMillisLong long mLastUsedTimeMillis; public ProviderData( - @NonNull String providerFlattenedComponentName, @NonNull String providerDisplayName, - @Nullable Icon icon, @NonNull List<Entry> credentialEntries, - @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry, - @CurrentTimeMillisLong long lastUsedTimeMillis) { + @NonNull String providerFlattenedComponentName) { mProviderFlattenedComponentName = providerFlattenedComponentName; - mProviderDisplayName = providerDisplayName; - mIcon = icon; - mCredentialEntries = credentialEntries; - mActionChips = actionChips; - mAuthenticationEntry = authenticationEntry; - mLastUsedTimeMillis = lastUsedTimeMillis; } - /** Returns the unique provider id. */ + /** + * Returns provider component name. + * It also serves as the unique identifier for this provider. + */ @NonNull public String getProviderFlattenedComponentName() { return mProviderFlattenedComponentName; } - @NonNull - public String getProviderDisplayName() { - return mProviderDisplayName; - } - - @Nullable - public Icon getIcon() { - return mIcon; - } - - @NonNull - public List<Entry> getCredentialEntries() { - return mCredentialEntries; - } - - @NonNull - public List<Entry> getActionChips() { - return mActionChips; - } - - @Nullable - public Entry getAuthenticationEntry() { - return mAuthenticationEntry; - } - - /** Returns the time when the provider was last used. */ - public @CurrentTimeMillisLong long getLastUsedTimeMillis() { - return mLastUsedTimeMillis; - } - protected ProviderData(@NonNull Parcel in) { String providerFlattenedComponentName = in.readString8(); mProviderFlattenedComponentName = providerFlattenedComponentName; AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName); - - String providerDisplayName = in.readString8(); - mProviderDisplayName = providerDisplayName; - AnnotationValidations.validate(NonNull.class, null, mProviderDisplayName); - - Icon icon = in.readTypedObject(Icon.CREATOR); - mIcon = icon; - - List<Entry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, Entry.CREATOR); - mCredentialEntries = credentialEntries; - AnnotationValidations.validate(NonNull.class, null, mCredentialEntries); - - List<Entry> actionChips = new ArrayList<>(); - in.readTypedList(actionChips, Entry.CREATOR); - mActionChips = actionChips; - AnnotationValidations.validate(NonNull.class, null, mActionChips); - - Entry authenticationEntry = in.readTypedObject(Entry.CREATOR); - mAuthenticationEntry = authenticationEntry; - - long lastUsedTimeMillis = in.readLong(); - mLastUsedTimeMillis = lastUsedTimeMillis; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mProviderFlattenedComponentName); - dest.writeString8(mProviderDisplayName); - dest.writeTypedObject(mIcon, flags); - dest.writeTypedList(mCredentialEntries); - dest.writeTypedList(mActionChips); - dest.writeTypedObject(mAuthenticationEntry, flags); - dest.writeLong(mLastUsedTimeMillis); } @Override public int describeContents() { return 0; } - - public static final @NonNull Creator<ProviderData> CREATOR = new Creator<ProviderData>() { - @Override - public ProviderData createFromParcel(@NonNull Parcel in) { - return new ProviderData(in); - } - - @Override - public ProviderData[] newArray(int size) { - return new ProviderData[size]; - } - }; - - /** - * Builder for {@link ProviderData}. - * - * @hide - */ - public static class Builder { - private @NonNull String mProviderFlattenedComponentName; - private @NonNull String mProviderDisplayName; - private @Nullable Icon mIcon; - private @NonNull List<Entry> mCredentialEntries = new ArrayList<>(); - private @NonNull List<Entry> mActionChips = new ArrayList<>(); - private @Nullable Entry mAuthenticationEntry = null; - private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L; - - /** Constructor with required properties. */ - public Builder(@NonNull String providerFlattenedComponentName, - @NonNull String providerDisplayName, - @Nullable Icon icon) { - mProviderFlattenedComponentName = providerFlattenedComponentName; - mProviderDisplayName = providerDisplayName; - mIcon = icon; - } - - /** Sets the unique provider id. */ - @NonNull - public Builder setProviderFlattenedComponentName(@NonNull String providerFlattenedComponentName) { - mProviderFlattenedComponentName = providerFlattenedComponentName; - return this; - } - - /** Sets the provider display name to be displayed to the user. */ - @NonNull - public Builder setProviderDisplayName(@NonNull String providerDisplayName) { - mProviderDisplayName = providerDisplayName; - return this; - } - - /** Sets the provider icon to be displayed to the user. */ - @NonNull - public Builder setIcon(@NonNull Icon icon) { - mIcon = icon; - return this; - } - - /** Sets the list of save / get credential entries to be displayed to the user. */ - @NonNull - public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) { - mCredentialEntries = credentialEntries; - return this; - } - - /** Sets the list of action chips to be displayed to the user. */ - @NonNull - public Builder setActionChips(@NonNull List<Entry> actionChips) { - mActionChips = actionChips; - return this; - } - - /** Sets the authentication entry to be displayed to the user. */ - @NonNull - public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) { - mAuthenticationEntry = authenticationEntry; - return this; - } - - /** Sets the time when the provider was last used. */ - @NonNull - public Builder setLastUsedTimeMillis(@CurrentTimeMillisLong long lastUsedTimeMillis) { - mLastUsedTimeMillis = lastUsedTimeMillis; - return this; - } - - /** Builds a {@link ProviderData}. */ - @NonNull - public ProviderData build() { - return new ProviderData(mProviderFlattenedComponentName, mProviderDisplayName, - mIcon, mCredentialEntries, - mActionChips, mAuthenticationEntry, mLastUsedTimeMillis); - } - } } diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java index 619b08ec9ca7..59d511838edc 100644 --- a/core/java/android/credentials/ui/RequestInfo.java +++ b/core/java/android/credentials/ui/RequestInfo.java @@ -69,6 +69,7 @@ public class RequestInfo implements Parcelable { private final boolean mIsFirstUsage; + // TODO: change to package name @NonNull private final String mAppDisplayName; diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl index 9c2aa6699334..8587348ef38b 100644 --- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl +++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 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. @@ -16,28 +16,28 @@ package android.hardware.fingerprint; /** - * A listener for the high-brightness mode (HBM) transitions. This allows other components to - * perform certain actions when the HBM is toggled on or off. For example, a display manager - * implementation can subscribe to these events from UdfpsController and adjust the display's - * refresh rate when the HBM is enabled. + * A callback for UDFPS refresh rate. This allows other components to + * perform certain actions when the refresh rate is enabled or disabled. + * For example, a display manager implementation can subscribe to these + * events from UdfpsController when refresh rate is enabled or disabled. * * @hide */ -oneway interface IUdfpsHbmListener { +oneway interface IUdfpsRefreshRateRequestCallback { /** - * UdfpsController will call this method when the HBM is enabled. + * Sets the appropriate display refresh rate for UDFPS. * - * @param displayId The displayId for which the HBM is enabled. See + * @param displayId The displayId for which the refresh rate should be set. See * {@link android.view.Display#getDisplayId()}. */ - void onHbmEnabled(int displayId); + void onRequestEnabled(int displayId); /** - * UdfpsController will call this method when the HBM is disabled. + * Unsets the appropriate display refresh rate for UDFPS. * - * @param displayId The displayId for which the HBM is disabled. See + * @param displayId The displayId for which the refresh rate should be unset. See * {@link android.view.Display#getDisplayId()}. */ - void onHbmDisabled(int displayId); + void onRequestDisabled(int displayId); } diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java index 20d6338ec927..27540966d0a9 100644 --- a/core/java/android/hardware/location/ActivityRecognitionHardware.java +++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java @@ -91,12 +91,16 @@ public class ActivityRecognitionHardware extends IActivityRecognitionHardware.St @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public String[] getSupportedActivities() { + super.getSupportedActivities_enforcePermission(); + return mSupportedActivities; } @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public boolean isActivitySupported(String activity) { + super.isActivitySupported_enforcePermission(); + int activityType = getActivityType(activity); return activityType != INVALID_ACTIVITY_TYPE; } @@ -104,12 +108,16 @@ public class ActivityRecognitionHardware extends IActivityRecognitionHardware.St @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public boolean registerSink(IActivityRecognitionHardwareSink sink) { + super.registerSink_enforcePermission(); + return mSinks.register(sink); } @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public boolean unregisterSink(IActivityRecognitionHardwareSink sink) { + super.unregisterSink_enforcePermission(); + return mSinks.unregister(sink); } @@ -117,6 +125,8 @@ public class ActivityRecognitionHardware extends IActivityRecognitionHardware.St @Override public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) { + super.enableActivityEvent_enforcePermission(); + int activityType = getActivityType(activity); if (activityType == INVALID_ACTIVITY_TYPE) { return false; @@ -134,6 +144,8 @@ public class ActivityRecognitionHardware extends IActivityRecognitionHardware.St @Override public boolean disableActivityEvent(String activity, int eventType) { + super.disableActivityEvent_enforcePermission(); + int activityType = getActivityType(activity); if (activityType == INVALID_ACTIVITY_TYPE) { return false; @@ -150,6 +162,8 @@ public class ActivityRecognitionHardware extends IActivityRecognitionHardware.St @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public boolean flush() { + super.flush_enforcePermission(); + int result = nativeFlush(); return result == NATIVE_SUCCESS_RESULT; } diff --git a/core/java/android/hardware/location/GeofenceHardwareService.java b/core/java/android/hardware/location/GeofenceHardwareService.java index 106bfd56a7af..99c1e16ad0c5 100644 --- a/core/java/android/hardware/location/GeofenceHardwareService.java +++ b/core/java/android/hardware/location/GeofenceHardwareService.java @@ -79,6 +79,8 @@ public class GeofenceHardwareService extends Service { @Override public int[] getMonitoringTypes() { + super.getMonitoringTypes_enforcePermission(); + return mGeofenceHardwareImpl.getMonitoringTypes(); } @@ -86,6 +88,8 @@ public class GeofenceHardwareService extends Service { @Override public int getStatusOfMonitoringType(int monitoringType) { + super.getStatusOfMonitoringType_enforcePermission(); + return mGeofenceHardwareImpl.getStatusOfMonitoringType(monitoringType); } @@ -95,6 +99,8 @@ public class GeofenceHardwareService extends Service { int monitoringType, GeofenceHardwareRequestParcelable request, IGeofenceHardwareCallback callback) { + super.addCircularFence_enforcePermission(); + checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType); return mGeofenceHardwareImpl.addCircularFence(monitoringType, request, callback); } @@ -103,6 +109,8 @@ public class GeofenceHardwareService extends Service { @Override public boolean removeGeofence(int id, int monitoringType) { + super.removeGeofence_enforcePermission(); + checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType); return mGeofenceHardwareImpl.removeGeofence(id, monitoringType); } @@ -111,6 +119,8 @@ public class GeofenceHardwareService extends Service { @Override public boolean pauseGeofence(int id, int monitoringType) { + super.pauseGeofence_enforcePermission(); + checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType); return mGeofenceHardwareImpl.pauseGeofence(id, monitoringType); } @@ -119,6 +129,8 @@ public class GeofenceHardwareService extends Service { @Override public boolean resumeGeofence(int id, int monitoringType, int monitorTransitions) { + super.resumeGeofence_enforcePermission(); + checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType); return mGeofenceHardwareImpl.resumeGeofence(id, monitoringType, monitorTransitions); } @@ -128,6 +140,8 @@ public class GeofenceHardwareService extends Service { public boolean registerForMonitorStateChangeCallback(int monitoringType, IGeofenceHardwareMonitorCallback callback) { + super.registerForMonitorStateChangeCallback_enforcePermission(); + checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType); return mGeofenceHardwareImpl.registerForMonitorStateChangeCallback(monitoringType, callback); @@ -138,6 +152,8 @@ public class GeofenceHardwareService extends Service { public boolean unregisterForMonitorStateChangeCallback(int monitoringType, IGeofenceHardwareMonitorCallback callback) { + super.unregisterForMonitorStateChangeCallback_enforcePermission(); + checkPermission(Binder.getCallingPid(), Binder.getCallingUid(), monitoringType); return mGeofenceHardwareImpl.unregisterForMonitorStateChangeCallback(monitoringType, callback); diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java index 36ac1a0cb21c..8a9213515122 100644 --- a/core/java/android/hardware/radio/ProgramSelector.java +++ b/core/java/android/hardware/radio/ProgramSelector.java @@ -533,7 +533,6 @@ public final class ProgramSelector implements Parcelable { mProgramType = in.readInt(); mPrimaryId = in.readTypedObject(Identifier.CREATOR); mSecondaryIds = in.createTypedArray(Identifier.CREATOR); - Arrays.sort(mSecondaryIds); if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) { throw new IllegalArgumentException("secondaryIds list must not contain nulls"); } diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 60d8cacd19be..7c2e518b8544 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -108,6 +108,34 @@ public class UsbDeviceConnection { } /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer, length); + } + } + + /** + * This is meant to be called by UsbRequest's queue() in order to synchronize on + * UsbDeviceConnection's mLock to prevent the connection being closed while queueing. + */ + /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) { + synchronized (mLock) { + if (!isOpen()) { + return false; + } + + return request.queueIfConnectionOpen(buffer); + } + } + + /** * Releases all system resources related to the device. * Once the object is closed it cannot be used again. * The client must call {@link UsbManager#openDevice} again diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java index 6ac5e8de8fa7..beb0f8d336d5 100644 --- a/core/java/android/hardware/usb/UsbRequest.java +++ b/core/java/android/hardware/usb/UsbRequest.java @@ -113,11 +113,13 @@ public class UsbRequest { * Releases all resources related to this request. */ public void close() { - if (mNativeContext != 0) { - mEndpoint = null; - mConnection = null; - native_close(); - mCloseGuard.close(); + synchronized (mLock) { + if (mNativeContext != 0) { + mEndpoint = null; + mConnection = null; + native_close(); + mCloseGuard.close(); + } } } @@ -191,10 +193,32 @@ public class UsbRequest { */ @Deprecated public boolean queue(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer, length); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new NullPointerException("invalid connection"); + } + boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean result; - if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P && length > MAX_USBFS_BUFFER_SIZE) { length = MAX_USBFS_BUFFER_SIZE; } @@ -243,6 +267,28 @@ public class UsbRequest { * @return true if the queueing operation succeeded */ public boolean queue(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + + // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent + // the connection being closed while queueing. + return connection.queueRequest(this, buffer); + } + + /** + * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over + * there, to prevent the connection being closed while queueing. + */ + /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) { + UsbDeviceConnection connection = mConnection; + if (connection == null || !connection.isOpen()) { + // The expected exception by CTS Verifier - USB Device test + throw new IllegalStateException("invalid connection"); + } + // Request need to be initialized Preconditions.checkState(mNativeContext != 0, "request is not initialized"); @@ -260,7 +306,7 @@ public class UsbRequest { mIsUsingNewQueue = true; wasQueued = native_queue(null, 0, 0); } else { - if (mConnection.getContext().getApplicationInfo().targetSdkVersion + if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, @@ -363,11 +409,12 @@ public class UsbRequest { * @return true if cancelling succeeded */ public boolean cancel() { - if (mConnection == null) { + UsbDeviceConnection connection = mConnection; + if (connection == null) { return false; } - return mConnection.cancelRequest(this); + return connection.cancelRequest(this); } /** @@ -382,7 +429,8 @@ public class UsbRequest { * @return true if cancelling succeeded. */ /* package */ boolean cancelIfOpen() { - if (mNativeContext == 0 || (mConnection != null && !mConnection.isOpen())) { + UsbDeviceConnection connection = mConnection; + if (mNativeContext == 0 || (connection != null && !connection.isOpen())) { Log.w(TAG, "Detected attempt to cancel a request on a connection which isn't open"); return false; diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java index 4f09beec81dd..95aecfe4c7af 100644 --- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java @@ -16,30 +16,29 @@ package android.inputmethodservice; +import static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED; + import android.annotation.AnyThread; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.RectF; import android.os.Bundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; -import android.view.inputmethod.DeleteGesture; -import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; -import android.view.inputmethod.InsertGesture; -import android.view.inputmethod.JoinOrSplitGesture; -import android.view.inputmethod.RemoveSpaceGesture; -import android.view.inputmethod.SelectGesture; -import android.view.inputmethod.SelectRangeGesture; +import android.view.inputmethod.ParcelableHandwritingGesture; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; +import android.view.inputmethod.TextBoundsInfo; +import android.view.inputmethod.TextBoundsInfoResult; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.IRemoteInputConnection; @@ -47,6 +46,7 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -98,6 +98,44 @@ final class IRemoteInputConnectionInvoker { }; /** + * Subclass of {@link ResultReceiver} used by + * {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing + * callback. + */ + private static final class TextBoundsInfoResultReceiver extends ResultReceiver { + @Nullable + private Consumer<TextBoundsInfoResult> mConsumer; + @Nullable + private Executor mExecutor; + + TextBoundsInfoResultReceiver(@NonNull Executor executor, + @NonNull Consumer<TextBoundsInfoResult> consumer) { + super(null); + mExecutor = executor; + mConsumer = consumer; + } + + @Override + protected void onReceiveResult(@TextBoundsInfoResult.ResultCode int resultCode, + @Nullable Bundle resultData) { + synchronized (this) { + if (mExecutor != null && mConsumer != null) { + final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult( + resultCode, TextBoundsInfo.createFromBundle(resultData)); + mExecutor.execute(() -> mConsumer.accept(textBoundsInfoResult)); + // provide callback only once. + clear(); + } + } + } + + private void clear() { + mExecutor = null; + mConsumer = null; + } + } + + /** * Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given * {@link IRemoteInputConnection}. * @@ -637,50 +675,19 @@ final class IRemoteInputConnectionInvoker { } /** - * Invokes one of {@link IRemoteInputConnection#performHandwritingSelectGesture}, - * {@link IRemoteInputConnection#performHandwritingSelectRangeGesture}, - * {@link IRemoteInputConnection#performHandwritingDeleteGesture}, - * {@link IRemoteInputConnection#performHandwritingDeleteRangeGesture}, - * {@link IRemoteInputConnection#performHandwritingInsertGesture}, - * {@link IRemoteInputConnection#performHandwritingRemoveSpaceGesture}, - * {@link IRemoteInputConnection#performHandwritingJoinOrSplitGesture}. + * Invokes {@link IRemoteInputConnection#performHandwritingGesture( + * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}. */ @AnyThread - public void performHandwritingGesture( - @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, - @Nullable IntConsumer consumer) { - + public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture, + @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { ResultReceiver resultReceiver = null; if (consumer != null) { Objects.requireNonNull(executor); resultReceiver = new IntResultReceiver(executor, consumer); } try { - if (gesture instanceof SelectGesture) { - mConnection.performHandwritingSelectGesture( - createHeader(), (SelectGesture) gesture, resultReceiver); - } else if (gesture instanceof SelectRangeGesture) { - mConnection.performHandwritingSelectRangeGesture( - createHeader(), (SelectRangeGesture) gesture, resultReceiver); - } else if (gesture instanceof InsertGesture) { - mConnection.performHandwritingInsertGesture( - createHeader(), (InsertGesture) gesture, resultReceiver); - } else if (gesture instanceof DeleteGesture) { - mConnection.performHandwritingDeleteGesture( - createHeader(), (DeleteGesture) gesture, resultReceiver); - } else if (gesture instanceof DeleteRangeGesture) { - mConnection.performHandwritingDeleteRangeGesture( - createHeader(), (DeleteRangeGesture) gesture, resultReceiver); - } else if (gesture instanceof RemoveSpaceGesture) { - mConnection.performHandwritingRemoveSpaceGesture( - createHeader(), (RemoveSpaceGesture) gesture, resultReceiver); - } else if (gesture instanceof JoinOrSplitGesture) { - mConnection.performHandwritingJoinOrSplitGesture( - createHeader(), (JoinOrSplitGesture) gesture, resultReceiver); - } else if (consumer != null && executor != null) { - executor.execute(() - -> consumer.accept(InputConnection.HANDWRITING_GESTURE_RESULT_UNSUPPORTED)); - } + mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver); } catch (RemoteException e) { if (consumer != null && executor != null) { executor.execute(() -> consumer.accept( @@ -736,6 +743,28 @@ final class IRemoteInputConnectionInvoker { } /** + * Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader, + * RectF, ResultReceiver)} + * @param rectF {@code rectF} parameter to be passed. + * @param executor {@code Executor} parameter to be passed. + * @param consumer {@code Consumer} parameter to be passed. + */ + @AnyThread + public void requestTextBoundsInfo( + @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<TextBoundsInfoResult> consumer) { + Objects.requireNonNull(executor); + Objects.requireNonNull(consumer); + + final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer); + try { + mConnection.requestTextBoundsInfo(createHeader(), rectF, resultReceiver); + } catch (RemoteException e) { + executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED))); + } + } + + /** * Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader, * InputContentInfo, int, Bundle, AndroidFuture)}. * diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index 09e86c4c9650..976e71f2b85a 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.RectF; import android.os.Bundle; import android.os.Handler; import android.util.Log; @@ -32,8 +33,10 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputContentInfo; +import android.view.inputmethod.ParcelableHandwritingGesture; import android.view.inputmethod.SurroundingText; import android.view.inputmethod.TextAttribute; +import android.view.inputmethod.TextBoundsInfoResult; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.CompletableFutureUtil; @@ -44,6 +47,7 @@ import com.android.internal.inputmethod.InputConnectionProtoDumper; import java.lang.ref.WeakReference; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -418,7 +422,8 @@ final class RemoteInputConnection implements InputConnection { public void performHandwritingGesture( @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) { - mInvoker.performHandwritingGesture(gesture, executor, consumer); + mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor, + consumer); } @AnyThread @@ -460,6 +465,13 @@ final class RemoteInputConnection implements InputConnection { } @AnyThread + public void requestTextBoundsInfo( + @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<TextBoundsInfoResult> consumer) { + mInvoker.requestTextBoundsInfo(rectF, executor, consumer); + } + + @AnyThread public Handler getHandler() { // Nothing should happen when called from input method. return null; diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 933769a352e9..8eaa5ad7fe94 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -69,6 +69,7 @@ interface IUserManager { boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne); UserInfo getProfileParent(int userId); boolean isSameProfileGroup(int userId, int otherUserHandle); + boolean isHeadlessSystemUserMode(); boolean isUserOfType(int userId, in String userType); @UnsupportedAppUsage UserInfo getUserInfo(int userId); diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 2afa87980c11..1673ade04150 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -561,9 +561,11 @@ public final class Parcel { */ public final void recycle() { if (mRecycled) { - Log.w(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: " + Log.wtf(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: " + Log.getStackTraceString(new Throwable()) + " Original recycle call (if DEBUG_RECYCLE): ", mStack); + + return; } mRecycled = true; diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java index 8a8045714d46..a2b0486c1df5 100644 --- a/core/java/android/os/Parcelable.java +++ b/core/java/android/os/Parcelable.java @@ -188,7 +188,7 @@ public interface Parcelable { * @return true if this parcelable is stable. * @hide */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS) default @Stability int getStability() { return PARCELABLE_STABILITY_LOCAL; } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 51dc643a73f3..3d20d6373ae2 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -91,7 +91,6 @@ import java.util.Set; public class UserManager { private static final String TAG = "UserManager"; - private static final boolean VERBOSE = false; @UnsupportedAppUsage private final IUserManager mService; @@ -104,6 +103,9 @@ public class UserManager { /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */ private String mProfileTypeOfProcessUser = null; + /** Whether the device is in headless system user mode; null until cached. */ + private static Boolean sIsHeadlessSystemUser = null; + /** * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user. * This type of user cannot be created; it can only pre-exist on first boot. @@ -1488,6 +1490,22 @@ public class UserManager { public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending"; /** + * Specifies if a user is not allowed to use 2g networks. + * + * <p>This restriction can only be set by a device owner or a profile owner of an + * organization-owned managed profile on the parent profile. + * In all cases, the setting applies globally on the device and will prevent the device from + * scanning for or connecting to 2g networks, except in the case of an emergency. + * + * <p>The default value is <code>false</code>. + * + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g"; + + /** * List of key values that can be passed into the various user restriction related methods * in {@link UserManager} & {@link DevicePolicyManager}. * Note: This is slightly different from the real set of user restrictions listed in {@link @@ -1568,6 +1586,7 @@ public class UserManager { DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, DISALLOW_WIFI_DIRECT, DISALLOW_ADD_WIFI_CONFIG, + DISALLOW_CELLULAR_2G, }) @Retention(RetentionPolicy.SOURCE) public @interface UserRestrictionKey {} @@ -2051,28 +2070,20 @@ public class UserManager { * @return whether the device is running in a headless system user mode. */ public static boolean isHeadlessSystemUserMode() { - final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER; - if (!Build.isDebuggable()) { - return realMode; - } - - final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY); - switch (emulatedMode) { - case SYSTEM_USER_MODE_EMULATION_FULL: - if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false"); - return false; - case SYSTEM_USER_MODE_EMULATION_HEADLESS: - if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true"); - return true; - case SYSTEM_USER_MODE_EMULATION_DEFAULT: - case "": // property not set - return realMode; - default: - Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property " - + SYSTEM_USER_MODE_EMULATION_PROPERTY + " (" + emulatedMode + "); using" - + " default value (headless=" + realMode + ")"); - return realMode; + // No need for synchronization. Once it becomes non-null, it'll be non-null forever. + // (Its value is determined when UMS is constructed and cannot change.) + // Worst case we might end up calling the AIDL method multiple times but that's fine. + if (sIsHeadlessSystemUser == null) { + // Unfortunately this API is static, but the property no longer is. So go fetch the UMS. + try { + final IUserManager service = IUserManager.Stub.asInterface( + ServiceManager.getService(Context.USER_SERVICE)); + sIsHeadlessSystemUser = service.isHeadlessSystemUserMode(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } } + return sIsHeadlessSystemUser; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 897b7c3afe54..fab6f7b97790 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9876,6 +9876,13 @@ public final class Settings { "fingerprint_side_fps_auth_downtime"; /** + * Whether or not a SFPS device is required to be interactive for auth to unlock the device. + * @hide + */ + public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED = + "sfps_require_screen_on_to_auth_enabled"; + + /** * Whether or not debugging is enabled. * @hide */ diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java index b4010a4eefda..0f7c9b63dac0 100644 --- a/core/java/android/service/autofill/FillRequest.java +++ b/core/java/android/service/autofill/FillRequest.java @@ -111,6 +111,12 @@ public final class FillRequest implements Parcelable { */ public static final @RequestFlags int FLAG_IME_SHOWING = 0x80; + /** + * Indicates whether autofill session should reset the fill dialog state. + * @hide + */ + public static final @RequestFlags int FLAG_RESET_FILL_DIALOG_STATE = 0x100; + /** @hide */ public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE; @@ -208,7 +214,8 @@ public final class FillRequest implements Parcelable { FLAG_PASSWORD_INPUT_TYPE, FLAG_VIEW_NOT_FOCUSED, FLAG_SUPPORTS_FILL_DIALOG, - FLAG_IME_SHOWING + FLAG_IME_SHOWING, + FLAG_RESET_FILL_DIALOG_STATE }) @Retention(RetentionPolicy.SOURCE) @DataClass.Generated.Member @@ -236,6 +243,8 @@ public final class FillRequest implements Parcelable { return "FLAG_SUPPORTS_FILL_DIALOG"; case FLAG_IME_SHOWING: return "FLAG_IME_SHOWING"; + case FLAG_RESET_FILL_DIALOG_STATE: + return "FLAG_RESET_FILL_DIALOG_STATE"; default: return Integer.toHexString(value); } } @@ -312,7 +321,8 @@ public final class FillRequest implements Parcelable { | FLAG_PASSWORD_INPUT_TYPE | FLAG_VIEW_NOT_FOCUSED | FLAG_SUPPORTS_FILL_DIALOG - | FLAG_IME_SHOWING); + | FLAG_IME_SHOWING + | FLAG_RESET_FILL_DIALOG_STATE); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; this.mDelayedFillIntentSender = delayedFillIntentSender; @@ -473,7 +483,8 @@ public final class FillRequest implements Parcelable { | FLAG_PASSWORD_INPUT_TYPE | FLAG_VIEW_NOT_FOCUSED | FLAG_SUPPORTS_FILL_DIALOG - | FLAG_IME_SHOWING); + | FLAG_IME_SHOWING + | FLAG_RESET_FILL_DIALOG_STATE); this.mInlineSuggestionsRequest = inlineSuggestionsRequest; this.mDelayedFillIntentSender = delayedFillIntentSender; @@ -495,10 +506,10 @@ public final class FillRequest implements Parcelable { }; @DataClass.Generated( - time = 1647856966565L, + time = 1663290803064L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java", - inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final int INVALID_REQUEST_ID\nprivate final int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java index 559b1caab87c..e330d1e94134 100644 --- a/core/java/android/service/credentials/CreateCredentialResponse.java +++ b/core/java/android/service/credentials/CreateCredentialResponse.java @@ -35,18 +35,22 @@ import java.util.Objects; public final class CreateCredentialResponse implements Parcelable { private final @Nullable CharSequence mHeader; private final @NonNull List<SaveEntry> mSaveEntries; + private final @Nullable Action mRemoteSaveEntry; + //TODO : Add actions if needed private CreateCredentialResponse(@NonNull Parcel in) { mHeader = in.readCharSequence(); List<SaveEntry> saveEntries = new ArrayList<>(); in.readTypedList(saveEntries, SaveEntry.CREATOR); mSaveEntries = saveEntries; + mRemoteSaveEntry = in.readTypedObject(Action.CREATOR); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeCharSequence(mHeader); dest.writeTypedList(mSaveEntries); + dest.writeTypedObject(mRemoteSaveEntry, flags); } @Override @@ -69,11 +73,13 @@ public final class CreateCredentialResponse implements Parcelable { /* package-private */ CreateCredentialResponse( @Nullable CharSequence header, - @NonNull List<SaveEntry> saveEntries) { + @NonNull List<SaveEntry> saveEntries, + @Nullable Action remoteSaveEntry) { this.mHeader = header; this.mSaveEntries = saveEntries; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mSaveEntries); + this.mRemoteSaveEntry = remoteSaveEntry; } /** Returns the header to be displayed on the UI. */ @@ -86,6 +92,11 @@ public final class CreateCredentialResponse implements Parcelable { return mSaveEntries; } + /** Returns the remote save entry to be displayed on the UI. */ + public @NonNull Action getRemoteSaveEntry() { + return mRemoteSaveEntry; + } + /** * A builder for {@link CreateCredentialResponse} */ @@ -94,6 +105,7 @@ public final class CreateCredentialResponse implements Parcelable { private @Nullable CharSequence mHeader; private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>(); + private @Nullable Action mRemoteSaveEntry; /** Sets the header to be displayed on the UI. */ public @NonNull Builder setHeader(@Nullable CharSequence header) { @@ -126,6 +138,14 @@ public final class CreateCredentialResponse implements Parcelable { } /** + * Sets a remote save entry to be shown on the UI. + */ + public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) { + mRemoteSaveEntry = remoteSaveEntry; + return this; + } + + /** * Builds the instance. * * @throws IllegalArgumentException If {@code saveEntries} is empty. @@ -135,7 +155,8 @@ public final class CreateCredentialResponse implements Parcelable { + "not be empty"); return new CreateCredentialResponse( mHeader, - mSaveEntries); + mSaveEntries, + mRemoteSaveEntry); } } } diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java index a3fa979a6188..1d4ac25eb284 100644 --- a/core/java/android/service/credentials/CredentialEntry.java +++ b/core/java/android/service/credentials/CredentialEntry.java @@ -172,9 +172,11 @@ public final class CredentialEntry implements Parcelable { * {@code credential}, or the {@code pendingIntent}. */ public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) { - Preconditions.checkState(pendingIntent != null && mCredential != null, - "credential is already set. Cannot set both the pendingIntent " - + "and the credential"); + if (pendingIntent != null) { + Preconditions.checkState(mCredential != null, + "credential is already set. Cannot set both the pendingIntent " + + "and the credential"); + } mPendingIntent = pendingIntent; return this; } @@ -186,9 +188,11 @@ public final class CredentialEntry implements Parcelable { * the {@code pendingIntent}, or the {@code credential}. */ public @NonNull Builder setCredential(@Nullable Credential credential) { - Preconditions.checkState(credential != null && mPendingIntent != null, - "pendingIntent is already set. Cannot set both the " - + "pendingIntent and the credential"); + if (credential != null) { + Preconditions.checkState(mPendingIntent != null, + "pendingIntent is already set. Cannot set both the " + + "pendingIntent and the credential"); + } mCredential = credential; return this; } diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java index 2cce169e7a58..ab5b5240dbfa 100644 --- a/core/java/android/service/credentials/CredentialsDisplayContent.java +++ b/core/java/android/service/credentials/CredentialsDisplayContent.java @@ -43,12 +43,17 @@ public final class CredentialsDisplayContent implements Parcelable { /** List of provider actions to be displayed on the UI. */ private final @NonNull List<Action> mActions; + /** Remote credential entry to get the response from a different device. */ + private final @Nullable Action mRemoteCredentialEntry; + private CredentialsDisplayContent(@Nullable CharSequence header, @NonNull List<CredentialEntry> credentialEntries, - @NonNull List<Action> actions) { + @NonNull List<Action> actions, + @Nullable Action remoteCredentialEntry) { mHeader = header; mCredentialEntries = credentialEntries; mActions = actions; + mRemoteCredentialEntry = remoteCredentialEntry; } private CredentialsDisplayContent(@NonNull Parcel in) { @@ -59,6 +64,7 @@ public final class CredentialsDisplayContent implements Parcelable { List<Action> actions = new ArrayList<>(); in.readTypedList(actions, Action.CREATOR); mActions = actions; + mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR); } public static final @NonNull Creator<CredentialsDisplayContent> CREATOR = @@ -84,6 +90,7 @@ public final class CredentialsDisplayContent implements Parcelable { dest.writeCharSequence(mHeader); dest.writeTypedList(mCredentialEntries, flags); dest.writeTypedList(mActions, flags); + dest.writeTypedObject(mRemoteCredentialEntry, flags); } /** @@ -108,12 +115,20 @@ public final class CredentialsDisplayContent implements Parcelable { } /** + * Returns the remote credential entry to be displayed on the UI. + */ + public @Nullable Action getRemoteCredentialEntry() { + return mRemoteCredentialEntry; + } + + /** * Builds an instance of {@link CredentialsDisplayContent}. */ public static final class Builder { - private CharSequence mHeader = null; + private CharSequence mHeader; private List<CredentialEntry> mCredentialEntries = new ArrayList<>(); private List<Action> mActions = new ArrayList<>(); + private Action mRemoteCredentialEntry; /** * Sets the header to be displayed on the UI. @@ -124,6 +139,14 @@ public final class CredentialsDisplayContent implements Parcelable { } /** + * Sets the remote credential entry to be displayed on the UI. + */ + public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) { + mRemoteCredentialEntry = remoteCredentialEntry; + return this; + } + + /** * Adds a {@link CredentialEntry} to the list of entries to be displayed on * the UI. * @@ -185,7 +208,8 @@ public final class CredentialsDisplayContent implements Parcelable { throw new IllegalStateException("credentialEntries and actions must not both " + "be empty"); } - return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions); + return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions, + mRemoteCredentialEntry); } } } diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java index abe51d43bc48..55ff6ff9755f 100644 --- a/core/java/android/service/credentials/SaveEntry.java +++ b/core/java/android/service/credentials/SaveEntry.java @@ -17,17 +17,11 @@ package android.service.credentials; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.PendingIntent; import android.app.slice.Slice; -import android.credentials.Credential; import android.os.Parcel; import android.os.Parcelable; -import com.android.internal.util.Preconditions; - -import java.util.Objects; - /** * An entry to be shown on the UI. This entry represents where the credential to be created will * be stored. Examples include user's account, family group etc. @@ -36,13 +30,11 @@ import java.util.Objects; */ public final class SaveEntry implements Parcelable { private final @NonNull Slice mSlice; - private final @Nullable PendingIntent mPendingIntent; - private final @Nullable Credential mCredential; + private final @NonNull PendingIntent mPendingIntent; private SaveEntry(@NonNull Parcel in) { mSlice = in.readTypedObject(Slice.CREATOR); mPendingIntent = in.readTypedObject(PendingIntent.CREATOR); - mCredential = in.readTypedObject(Credential.CREATOR); } public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() { @@ -66,18 +58,23 @@ public final class SaveEntry implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeTypedObject(mSlice, flags); dest.writeTypedObject(mPendingIntent, flags); - dest.writeTypedObject(mCredential, flags); } - /* package-private */ SaveEntry( + /** + * Constructs a save entry to be displayed on the UI. + * + * @param slice the display content to be displayed on the UI, along with this entry + * @param pendingIntent the intent to be invoked when the user selects this entry + */ + public SaveEntry( @NonNull Slice slice, - @Nullable PendingIntent pendingIntent, - @Nullable Credential credential) { + @NonNull PendingIntent pendingIntent) { this.mSlice = slice; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mSlice); this.mPendingIntent = pendingIntent; - this.mCredential = credential; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPendingIntent); } /** Returns the content to be displayed with this save entry on the UI. */ @@ -86,76 +83,7 @@ public final class SaveEntry implements Parcelable { } /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */ - public @Nullable PendingIntent getPendingIntent() { + public @NonNull PendingIntent getPendingIntent() { return mPendingIntent; } - - /** Returns the credential produced by the {@link CreateCredentialRequest}. */ - public @Nullable Credential getCredential() { - return mCredential; - } - - /** - * A builder for {@link SaveEntry}. - */ - public static final class Builder { - - private @NonNull Slice mSlice; - private @Nullable PendingIntent mPendingIntent; - private @Nullable Credential mCredential; - - /** - * Builds the instance. - * @param slice the content to be displayed with this save entry - * - * @throws NullPointerException If {@code slice} is null. - */ - public Builder(@NonNull Slice slice) { - mSlice = Objects.requireNonNull(slice, "slice must not be null"); - } - - /** - * Sets the pendingIntent to be invoked when this entry is selected by the user. - * - * @throws IllegalStateException If {@code credential} is already set. Must only set either - * {@code credential}, or the {@code pendingIntent}. - */ - public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) { - Preconditions.checkState(pendingIntent != null - && mCredential != null, "credential is already set. Must only set " - + "either the pendingIntent or the credential"); - mPendingIntent = pendingIntent; - return this; - } - - /** - * Sets the credential to be returned when this entry is selected by the user. - * - * @throws IllegalStateException If {@code pendingIntent} is already set. Must only - * set either the {@code pendingIntent}, or {@code credential}. - */ - public @NonNull Builder setCredential(@Nullable Credential credential) { - Preconditions.checkState(credential != null && mPendingIntent != null, - "pendingIntent is already set. Must only set either the credential " - + "or the pendingIntent"); - mCredential = credential; - return this; - } - - /** - * Builds the instance. - * - * @throws IllegalStateException if both {@code pendingIntent} and {@code credential} - * are null. - */ - public @NonNull SaveEntry build() { - Preconditions.checkState(mPendingIntent == null && mCredential == null, - "pendingIntent and credential both must not be null. Must set " - + "either the pendingIntnet or the credential"); - return new SaveEntry( - mSlice, - mPendingIntent, - mCredential); - } - } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index c6cd708c5967..37fc9f288c3e 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -575,6 +575,7 @@ public abstract class WallpaperService extends Service { */ public void reportEngineShown(boolean waitForEngineShown) { if (mIWallpaperEngine.mShownReported) return; + Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown); if (!waitForEngineShown) { Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN); mCaller.removeMessages(MSG_REPORT_SHOWN); diff --git a/core/java/android/text/GraphemeClusterSegmentFinder.java b/core/java/android/text/GraphemeClusterSegmentFinder.java index 3335751e8817..656774f66792 100644 --- a/core/java/android/text/GraphemeClusterSegmentFinder.java +++ b/core/java/android/text/GraphemeClusterSegmentFinder.java @@ -49,6 +49,7 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder { @Override public int previousStartBoundary(@IntRange(from = 0) int offset) { + if (offset == 0) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE); return boundary == -1 ? DONE : boundary; @@ -56,6 +57,7 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder { @Override public int previousEndBoundary(@IntRange(from = 0) int offset) { + if (offset == 0) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE); // Check that there is another cursor position before, otherwise this is not a valid @@ -69,6 +71,7 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder { @Override public int nextStartBoundary(@IntRange(from = 0) int offset) { + if (offset == mText.length()) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER); // Check that there is another cursor position after, otherwise this is not a valid @@ -82,6 +85,7 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder { @Override public int nextEndBoundary(@IntRange(from = 0) int offset) { + if (offset == mText.length()) return DONE; int boundary = mTextPaint.getTextRunCursor( mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER); return boundary == -1 ? DONE : boundary; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 1337d6a87df8..54ec07edd3ee 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -3000,6 +3000,18 @@ public abstract class Layout { } /** + * Returns the BiDi level of this run. + * + * @param runIndex the index of the BiDi run + * @return the BiDi level of this run. + * @hide + */ + @IntRange(from = 0) + public int getRunLevel(int runIndex) { + return (mDirections[runIndex * 2 + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK; + } + + /** * Returns true if the BiDi run is RTL. * * @param runIndex the index of the BiDi run diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java index c21c5774fa0a..be0094b28509 100644 --- a/core/java/android/text/SegmentFinder.java +++ b/core/java/android/text/SegmentFinder.java @@ -19,6 +19,13 @@ package android.text; import android.annotation.IntRange; import android.graphics.RectF; +import androidx.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +import java.util.Arrays; +import java.util.Objects; + /** * Finds text segment boundaries within text. Subclasses can implement different types of text * segments. Grapheme clusters and words are examples of possible text segments. These are @@ -63,4 +70,144 @@ public abstract class SegmentFinder { * character offset, or {@code DONE} if there are none. */ public abstract int nextEndBoundary(@IntRange(from = 0) int offset); + + /** + * The default {@link SegmentFinder} implementation based on given segment ranges. + */ + public static class DefaultSegmentFinder extends SegmentFinder { + private final int[] mSegments; + + /** + * Create a SegmentFinder with segments stored in an array, where i-th segment's start is + * stored at segments[2 * i] and end is stored at segments[2 * i + 1] respectively. + * + * <p> It is required that segments do not overlap, and are already sorted by their start + * indices. </p> + * @param segments the array that stores the segment ranges. + * @throws IllegalArgumentException if the given segments array's length is not even; the + * given segments are not sorted or there are segments overlap with others. + */ + public DefaultSegmentFinder(@NonNull int[] segments) { + checkSegmentsValid(segments); + mSegments = segments; + } + + /** {@inheritDoc} */ + @Override + public int previousStartBoundary(@IntRange(from = 0) int offset) { + return findPrevious(offset, /* isStart = */ true); + } + + /** {@inheritDoc} */ + @Override + public int previousEndBoundary(@IntRange(from = 0) int offset) { + return findPrevious(offset, /* isStart = */ false); + } + + /** {@inheritDoc} */ + @Override + public int nextStartBoundary(@IntRange(from = 0) int offset) { + return findNext(offset, /* isStart = */ true); + } + + /** {@inheritDoc} */ + @Override + public int nextEndBoundary(@IntRange(from = 0) int offset) { + return findNext(offset, /* isStart = */ false); + } + + private int findNext(int offset, boolean isStart) { + if (offset < 0) return DONE; + if (mSegments.length < 1 || offset > mSegments[mSegments.length - 1]) return DONE; + + if (offset < mSegments[0]) { + return isStart ? mSegments[0] : mSegments[1]; + } + + int index = Arrays.binarySearch(mSegments, offset); + if (index >= 0) { + // mSegments may have duplicate elements (The previous segments end equals + // to the following segments start.) Move the index forwards since we are searching + // for the next segment. + if (index + 1 < mSegments.length && mSegments[index + 1] == offset) { + index = index + 1; + } + // Point the index to the first segment boundary larger than the given offset. + index += 1; + } else { + // binarySearch returns the insertion point, it's the first segment boundary larger + // than the given offset. + index = -(index + 1); + } + if (index >= mSegments.length) return DONE; + + // +---------------------------------------+ + // | | isStart | isEnd | + // |---------------+-----------+-----------| + // | indexIsStart | index | index + 1 | + // |---------------+-----------+-----------| + // | indexIsEnd | index + 1 | index | + // +---------------------------------------+ + boolean indexIsStart = index % 2 == 0; + if (isStart != indexIsStart) { + return (index + 1 < mSegments.length) ? mSegments[index + 1] : DONE; + } + return mSegments[index]; + } + + private int findPrevious(int offset, boolean isStart) { + if (mSegments.length < 1 || offset < mSegments[0]) return DONE; + + if (offset > mSegments[mSegments.length - 1]) { + return isStart ? mSegments[mSegments.length - 2] : mSegments[mSegments.length - 1]; + } + + int index = Arrays.binarySearch(mSegments, offset); + if (index >= 0) { + // mSegments may have duplicate elements (when the previous segments end equal + // to the following segments start). Move the index backwards since we are searching + // for the previous segment. + if (index > 0 && mSegments[index - 1] == offset) { + index = index - 1; + } + // Point the index to the first segment boundary smaller than the given offset. + index -= 1; + } else { + // binarySearch returns the insertion point, insertionPoint - 1 is the first + // segment boundary smaller than the given offset. + index = -(index + 1) - 1; + } + if (index < 0) return DONE; + + // +---------------------------------------+ + // | | isStart | isEnd | + // |---------------+-----------+-----------| + // | indexIsStart | index | index - 1 | + // |---------------+-----------+-----------| + // | indexIsEnd | index - 1 | index | + // +---------------------------------------+ + boolean indexIsStart = index % 2 == 0; + if (isStart != indexIsStart) { + return (index > 0) ? mSegments[index - 1] : DONE; + } + return mSegments[index]; + } + + private static void checkSegmentsValid(int[] segments) { + Objects.requireNonNull(segments); + Preconditions.checkArgument(segments.length % 2 == 0, + "the length of segments must be even"); + if (segments.length == 0) return; + int lastSegmentEnd = Integer.MIN_VALUE; + for (int index = 0; index < segments.length; index += 2) { + if (segments[index] < lastSegmentEnd) { + throw new IllegalArgumentException("segments can't overlap"); + } + if (segments[index] >= segments[index + 1]) { + throw new IllegalArgumentException("the segment range can't be empty"); + } + lastSegmentEnd = segments[index + 1]; + } + } + } } diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 596e4914896a..ff66e5f28275 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -2331,7 +2331,8 @@ public class TextUtils { return trimmed; } - private static boolean isNewline(int codePoint) { + /** @hide */ + public static boolean isNewline(int codePoint) { int type = Character.getType(codePoint); return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR || codePoint == LINE_FEED_CODE_POINT; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index d1f05ec0c05e..7b6a6d29baf8 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -103,6 +103,25 @@ public class FeatureFlagUtils { public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui"; /** + * Enable new shortcut list UI + * @hide + */ + public static final String SETTINGS_NEW_KEYBOARD_SHORTCUT = "settings_new_keyboard_shortcut"; + + /** + * Enable new modifier key settings UI + * @hide + */ + public static final String SETTINGS_NEW_KEYBOARD_MODIFIER_KEY = + "settings_new_keyboard_modifier_key"; + + /** + * Enable new trackpad settings UI + * @hide + */ + public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad"; + + /** * Enable the new pages which is implemented with SPA. * @hide */ @@ -143,6 +162,9 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true"); DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false"); + DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false"); + DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false"); + DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false"); DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); } @@ -158,6 +180,9 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE); PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI); + PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT); + PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); + PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD); } /** diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java new file mode 100644 index 000000000000..719c614f42f0 --- /dev/null +++ b/core/java/android/view/HandwritingDelegateConfiguration.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.annotation.IdRes; +import android.annotation.NonNull; + +/** + * Configuration for a view to act as a handwriting initiation delegate. This allows handwriting + * mode for a delegator editor view to be initiated by stylus movement on the delegate view. + * + * <p>If a stylus {@link MotionEvent} occurs within the delegate view's bounds, the callback + * returned by {@link #getInitiationCallback()} will be called. The callback implementation is + * expected to show and focus the delegator editor view. If a view with identifier matching {@link + * #getDelegatorViewId()} creates an input connection while the same stylus {@link MotionEvent} + * sequence is ongoing, handwriting mode will be initiated for that view. + * + * <p>A common use case is a custom view which looks like a text editor but does not actually + * support text editing itself, and clicking on the custom view causes an EditText to be shown. To + * support handwriting initiation in this case, {@link View#setHandwritingDelegateConfiguration} can + * be called on the custom view to configure it as a delegate, and set the EditText as the delegator + * by passing the EditText's identifier as the {@code delegatorViewId}. The {@code + * initiationCallback} implementation is typically the same as the click listener implementation + * which shows the EditText. + */ +public class HandwritingDelegateConfiguration { + @IdRes private final int mDelegatorViewId; + @NonNull private final Runnable mInitiationCallback; + + /** + * Constructs a HandwritingDelegateConfiguration instance. + * + * @param delegatorViewId identifier of the delegator editor view for which handwriting mode + * should be initiated + * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within + * this view's bounds. This will be called from the UI thread. + */ + public HandwritingDelegateConfiguration( + @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) { + mDelegatorViewId = delegatorViewId; + mInitiationCallback = initiationCallback; + } + + /** + * Returns the identifier of the delegator editor view for which handwriting mode should be + * initiated. + */ + public int getDelegatorViewId() { + return mDelegatorViewId; + } + + /** + * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within + * the delegate view's bounds. The callback should only be called from the UI thread. + */ + @NonNull + public Runnable getInitiationCallback() { + return mInitiationCallback; + } +} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index a0a07b30662f..2e4073e9cfd0 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -161,6 +162,15 @@ public class HandwritingInitiator { if (candidateView != null) { if (candidateView == getConnectedView()) { startHandwriting(candidateView); + } else if (candidateView.getHandwritingDelegateConfiguration() != null) { + mState.mDelegatorViewId = + candidateView + .getHandwritingDelegateConfiguration() + .getDelegatorViewId(); + candidateView + .getHandwritingDelegateConfiguration() + .getInitiationCallback() + .run(); } else { if (candidateView.getRevealOnFocusHint()) { candidateView.setRevealOnFocusHint(false); @@ -259,8 +269,10 @@ public class HandwritingInitiator { } final Rect handwritingArea = getViewHandwritingArea(connectedView); - if (isInHandwritingArea(handwritingArea, mState.mStylusDownX, - mState.mStylusDownY, connectedView)) { + if ((mState.mDelegatorViewId != View.NO_ID + && mState.mDelegatorViewId == connectedView.getId()) + || isInHandwritingArea( + handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) { startHandwriting(connectedView); } else { mState.mShouldInitHandwriting = false; @@ -287,6 +299,11 @@ public class HandwritingInitiator { if (!view.isAutoHandwritingEnabled()) { return false; } + // The view may be a handwriting initiation delegate, in which case it is not the editor + // view for which handwriting would be started. However, in almost all cases, the return + // values of View#isStylusHandwritingAvailable will be the same for the delegate view and + // the delegator editor view. So the delegate view can be used to decide whether handwriting + // should be triggered. return view.isStylusHandwritingAvailable(); } @@ -473,6 +490,13 @@ public class HandwritingInitiator { * built InputConnection. */ private boolean mExceedHandwritingSlop; + /** + * If the current ongoing stylus MotionEvent sequence started over a handwriting initiation + * delegate view, then this is the view identifier of the corresponding delegator view. If + * the delegator view creates an input connection while the MotionEvent sequence is still + * ongoing, then handwriting mode will be initiated for the delegator view. + */ + @IdRes private int mDelegatorViewId = View.NO_ID; /** The pointer id of the stylus pointer that is being tracked. */ private final int mStylusPointerId; diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 198ac9d81ce8..b24303b41abd 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -121,16 +121,23 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private static final boolean DEBUG = false; private static final boolean DEBUG_POSITION = false; - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead") final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<>(); final int[] mLocation = new int[2]; - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@link SurfaceHolder#lockCanvas} instead") final ReentrantLock mSurfaceLock = new ReentrantLock(); - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@link SurfaceHolder#getSurface} instead") final Surface mSurface = new Surface(); // Current surface in use - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023, + publicAlternatives = "Use {@link View#getVisibility} instead") boolean mDrawingStopped = true; // We use this to track if the application has produced a frame // in to the Surface. Up until that point, we should be careful not to punch @@ -156,13 +163,16 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall int mSubLayer = APPLICATION_MEDIA_SUBLAYER; int mRequestedSubLayer = APPLICATION_MEDIA_SUBLAYER; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023, + publicAlternatives = "Use {@link SurfaceHolder#isCreating} instead") boolean mIsCreating = false; private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener = this::updateSurface; - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Rely on {@link ViewTreeObserver#dispatchOnPreDraw} instead") private final ViewTreeObserver.OnPreDrawListener mDrawListener = () -> { // reposition ourselves where the surface is mHaveFrame = getWidth() > 0 && getHeight() > 0; @@ -176,24 +186,32 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall boolean mViewVisibility = false; boolean mWindowStopped = false; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023, + publicAlternatives = "Use {@link View#getWidth} instead") int mRequestedWidth = -1; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023, + publicAlternatives = "Use {@link View#getHeight} instead") int mRequestedHeight = -1; /* Set SurfaceView's format to 565 by default to maintain backward * compatibility with applications assuming this format. */ - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code SurfaceHolder.Callback#surfaceChanged} instead") int mRequestedFormat = PixelFormat.RGB_565; float mAlpha = 1f; boolean mClipSurfaceToBounds; int mBackgroundColor = Color.BLACK; - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@link View#getWidth} and {@link View#getHeight} to " + + "determine if the SurfaceView is onscreen and has a frame") boolean mHaveFrame = false; boolean mSurfaceCreated = false; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023, + publicAlternatives = "Time {@link SurfaceHolder#lockCanvas} instead") long mLastLockTime = 0; boolean mVisible = false; @@ -202,9 +220,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall int mSurfaceWidth = -1; int mSurfaceHeight = -1; float mCornerRadius; - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@code SurfaceHolder.Callback#surfaceChanged} " + + "instead") int mFormat = -1; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023, + publicAlternatives = "Use {@link SurfaceHolder#getSurfaceFrame} instead") final Rect mSurfaceFrame = new Rect(); int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1; @SurfaceControl.BufferTransform int mTransformHint = 0; @@ -1410,7 +1432,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * @return true if the surface has dimensions that are fixed in size * @hide */ - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Track {@link SurfaceHolder#setFixedSize} instead") public boolean isFixedSize() { return (mRequestedWidth != -1 || mRequestedHeight != -1); } @@ -1446,7 +1470,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall updateBackgroundColor(t); } - @UnsupportedAppUsage + @UnsupportedAppUsage( + maxTargetSdk = Build.VERSION_CODES.TIRAMISU, + publicAlternatives = "Use {@link SurfaceView#getHolder} instead") private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { private static final String LOG_TAG = "SurfaceHolder"; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7b6ebf7cd9b8..49d9e67ce51d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5063,6 +5063,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean mHoveringTouchDelegate = false; /** + * Configuration for this view to act as a handwriting initiation delegate. This allows + * handwriting mode for a delegator editor view to be initiated by stylus movement on this + * delegate view. + */ + private HandwritingDelegateConfiguration mHandwritingDelegateConfiguration; + + /** * Solid color to use as a background when creating the drawing cache. Enables * the cache to use 16 bit bitmaps instead of 32 bit. */ @@ -12255,6 +12262,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Configures this view to act as a handwriting initiation delegate. This allows handwriting + * mode for a delegator editor view to be initiated by stylus movement on this delegate view. + * + * <p>If {@code null} is passed, this view will no longer act as a handwriting initiation + * delegate. + */ + public void setHandwritingDelegateConfiguration( + @Nullable HandwritingDelegateConfiguration configuration) { + mHandwritingDelegateConfiguration = configuration; + if (configuration != null) { + setHandwritingArea(new Rect(0, 0, getWidth(), getHeight())); + } + } + + /** + * If this view has been configured as a handwriting initiation delegate, returns the delegate + * configuration. + */ + @Nullable + public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() { + return mHandwritingDelegateConfiguration; + } + + /** * Gets the coordinates of this view in the coordinate space of the * {@link Surface} that contains the view. * @@ -24205,7 +24236,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } rebuildOutline(); - if (onCheckIsTextEditor()) { + if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) { setHandwritingArea(new Rect(0, 0, newWidth, newHeight)); } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index ff4588a7bc9b..e664ebf4c484 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1257,7 +1257,7 @@ public final class ViewRootImpl implements ViewParent, mTmpFrames.attachedFrame = attachedFrame; mTmpFrames.sizeCompatScale = sizeCompatScale[0]; mInvSizeCompatScale = 1f / sizeCompatScale[0]; - } catch (RemoteException e) { + } catch (RemoteException | RuntimeException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java new file mode 100644 index 000000000000..85f5056e4116 --- /dev/null +++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.accessibility; + +import android.accessibilityservice.AccessibilityGestureEvent; +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.IAccessibilityServiceClient; +import android.accessibilityservice.MagnificationConfig; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.Context; +import android.graphics.Region; +import android.os.IBinder; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.inputmethod.EditorInfo; + +import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; +import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to + * interact with the windows in the display that this proxy represents. Proxying the default display + * or a display that is not tracked will throw an exception. Only the real user has access to global + * clients like SystemUI. + * + * <p> + * To register and unregister a proxy, use + * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} + * and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app + * that has registered the proxy dies, the system will remove the proxy. + * + * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods + * @hide + */ +@SystemApi +public abstract class AccessibilityDisplayProxy { + private static final String LOG_TAG = "AccessibilityDisplayProxy"; + private static final int INVALID_CONNECTION_ID = -1; + + private List<AccessibilityServiceInfo> mInstalledAndEnabledServices; + private Executor mExecutor; + private int mConnectionId = INVALID_CONNECTION_ID; + private int mDisplayId; + IAccessibilityServiceClient mServiceClient; + + /** + * Constructs an AccessibilityDisplayProxy instance. + * @param displayId the id of the display to proxy. + * @param executor the executor used to execute proxy callbacks. + * @param installedAndEnabledServices the list of infos representing the installed and + * enabled a11y services. + */ + public AccessibilityDisplayProxy(int displayId, @NonNull Executor executor, + @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) { + mDisplayId = displayId; + mExecutor = executor; + // Typically, the context is the Service context of an accessibility service. + // Context is used for ResolveInfo check, which a proxy won't have, IME input + // (FLAG_INPUT_METHOD_EDITOR), which the proxy doesn't need, and tracing + // A11yInteractionClient methods. + // TODO(254097475): Enable tracing, potentially without exposing Context. + mServiceClient = new IAccessibilityServiceClientImpl(null, mExecutor); + mInstalledAndEnabledServices = installedAndEnabledServices; + } + + /** + * Returns the id of the display being proxy-ed. + */ + public int getDisplayId() { + return mDisplayId; + } + + /** + * An IAccessibilityServiceClient that handles interrupts and accessibility events. + */ + private class IAccessibilityServiceClientImpl extends + AccessibilityService.IAccessibilityServiceClientWrapper { + + IAccessibilityServiceClientImpl(Context context, Executor executor) { + super(context, executor, new AccessibilityService.Callbacks() { + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + // TODO: call AccessiiblityProxy.onAccessibilityEvent + } + + @Override + public void onInterrupt() { + // TODO: call AccessiiblityProxy.onInterrupt + } + @Override + public void onServiceConnected() { + // TODO: send service infos and call AccessiiblityProxy.onProxyConnected + } + @Override + public void init(int connectionId, IBinder windowToken) { + mConnectionId = connectionId; + } + + @Override + public boolean onGesture(AccessibilityGestureEvent gestureInfo) { + return false; + } + + @Override + public boolean onKeyEvent(KeyEvent event) { + return false; + } + + @Override + public void onMagnificationChanged(int displayId, @NonNull Region region, + MagnificationConfig config) { + } + + @Override + public void onMotionEvent(MotionEvent event) { + } + + @Override + public void onTouchStateChanged(int displayId, int state) { + } + + @Override + public void onSoftKeyboardShowModeChanged(int showMode) { + } + + @Override + public void onPerformGestureResult(int sequence, boolean completedSuccessfully) { + } + + @Override + public void onFingerprintCapturingGesturesChanged(boolean active) { + } + + @Override + public void onFingerprintGesture(int gesture) { + } + + @Override + public void onAccessibilityButtonClicked(int displayId) { + } + + @Override + public void onAccessibilityButtonAvailabilityChanged(boolean available) { + } + + @Override + public void onSystemActionsChanged() { + } + + @Override + public void createImeSession(IAccessibilityInputMethodSessionCallback callback) { + } + + @Override + public void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting) { + } + }); + } + } +} diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 5433fa08ac18..423c560d5c57 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -1921,6 +1921,67 @@ public final class AccessibilityManager { } } + /** + * Registers an {@link AccessibilityDisplayProxy}, so this proxy can access UI content specific + * to its display. + * + * @param proxy the {@link AccessibilityDisplayProxy} to register. + * @return {@code true} if the proxy is successfully registered. + * + * @throws IllegalArgumentException if the proxy's display is not currently tracked by a11y, is + * {@link android.view.Display#DEFAULT_DISPLAY}, is or lower than + * {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed. + * + * @throws SecurityException if the app does not hold the + * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public boolean registerDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + + try { + return service.registerProxyForDisplay(proxy.mServiceClient, proxy.getDisplayId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Unregisters an {@link AccessibilityDisplayProxy}. + * + * @return {@code true} if the proxy is successfully unregistered. + * + * @throws SecurityException if the app does not hold the + * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + public boolean unregisterDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) { + final IAccessibilityManager service; + synchronized (mLock) { + service = getServiceLocked(); + if (service == null) { + return false; + } + } + try { + return service.unregisterProxyForDisplay(proxy.getDisplayId()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + private IAccessibilityManager getServiceLocked() { if (mService == null) { tryConnectToServiceLocked(null); diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 36fdcce4e1f2..a25194804987 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -109,9 +109,9 @@ interface IAccessibilityManager { oneway void setAccessibilityWindowAttributes(int displayId, int windowId, int userId, in AccessibilityWindowAttributes attributes); - // Requires Manifest.permission.MANAGE_ACCESSIBILITY + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)") boolean registerProxyForDisplay(IAccessibilityServiceClient proxy, int displayId); - // Requires Manifest.permission.MANAGE_ACCESSIBILITY + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)") boolean unregisterProxyForDisplay(int displayId); } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 70cfc3efc88a..ef683b70172c 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -19,6 +19,7 @@ package android.view.autofill; import static android.service.autofill.FillRequest.FLAG_IME_SHOWING; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; +import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE; import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; import static android.view.ContentInfo.SOURCE_AUTOFILL; @@ -734,7 +735,7 @@ public final class AutofillManager { * Autofill will automatically trigger a fill request after activity * start if there is any field is autofillable. But if there is a field that * triggered autofill, it is unnecessary to trigger again through - * AutofillManager#notifyViewEnteredForActivityStarted. + * AutofillManager#notifyViewEnteredForFillDialog. */ private AtomicBoolean mIsFillRequested; @@ -747,6 +748,10 @@ public final class AutofillManager { private final String[] mFillDialogEnabledHints; + // Tracked all views that have appeared, including views that there are no + // dataset in responses. Used to avoid request pre-fill request again and again. + private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>(); + /** @hide */ public interface AutofillClient { /** @@ -1192,6 +1197,16 @@ public final class AutofillManager { * @hide */ public void notifyViewEnteredForFillDialog(View v) { + synchronized (mLock) { + if (mTrackedViews != null) { + // To support the fill dialog can show for the autofillable Views in + // different pages but in the same Activity. We need to reset the + // mIsFillRequested flag to allow asking for a new FillRequest when + // user switches to other page + mTrackedViews.checkViewState(v.getAutofillId()); + } + } + // Skip if the fill request has been performed for a view. if (mIsFillRequested.get()) { return; @@ -1318,6 +1333,10 @@ public final class AutofillManager { } mForAugmentedAutofillOnly = false; } + + if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) { + flags |= FLAG_RESET_FILL_DIALOG_STATE; + } updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags); } addEnteredIdLocked(id); @@ -2217,6 +2236,7 @@ public final class AutofillManager { mIsFillRequested.set(false); mShowAutofillDialogCalled = false; mFillDialogTriggerIds = null; + mAllTrackedViews.clear(); if (resetEnteredIds) { mEnteredIds = null; } @@ -2776,14 +2796,9 @@ public final class AutofillManager { + ", mFillableIds=" + mFillableIds + ", mEnabled=" + mEnabled + ", mSessionId=" + mSessionId); - } + if (mEnabled && mSessionId == sessionId) { - if (saveOnAllViewsInvisible) { - mTrackedViews = new TrackedViews(trackedIds); - } else { - mTrackedViews = null; - } mSaveOnFinish = saveOnFinish; if (fillableIds != null) { if (mFillableIds == null) { @@ -2805,6 +2820,27 @@ public final class AutofillManager { mSaveTriggerId = saveTriggerId; setNotifyOnClickLocked(mSaveTriggerId, true); } + + if (!saveOnAllViewsInvisible) { + trackedIds = null; + } + + final ArraySet<AutofillId> allFillableIds = new ArraySet<>(); + if (mFillableIds != null) { + allFillableIds.addAll(mFillableIds); + } + if (trackedIds != null) { + for (AutofillId id : trackedIds) { + id.resetSessionId(); + allFillableIds.add(id); + } + } + + if (!allFillableIds.isEmpty()) { + mTrackedViews = new TrackedViews(trackedIds, Helper.toArray(allFillableIds)); + } else { + mTrackedViews = null; + } } } } @@ -3576,10 +3612,19 @@ public final class AutofillManager { */ private class TrackedViews { /** Visible tracked views */ - @Nullable private ArraySet<AutofillId> mVisibleTrackedIds; + @NonNull private final ArraySet<AutofillId> mVisibleTrackedIds; /** Invisible tracked views */ - @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds; + @NonNull private final ArraySet<AutofillId> mInvisibleTrackedIds; + + /** Visible tracked views for fill dialog */ + @NonNull private final ArraySet<AutofillId> mVisibleDialogTrackedIds; + + /** Invisible tracked views for fill dialog */ + @NonNull private final ArraySet<AutofillId> mInvisibleDialogTrackedIds; + + boolean mHasNewTrackedView; + boolean mIsTrackedSaveView; /** * Check if set is null or value is in set. @@ -3645,43 +3690,65 @@ public final class AutofillManager { * * @param trackedIds The views to be tracked */ - TrackedViews(@Nullable AutofillId[] trackedIds) { - final AutofillClient client = getClient(); - if (!ArrayUtils.isEmpty(trackedIds) && client != null) { - final boolean[] isVisible; - - if (client.autofillClientIsVisibleForAutofill()) { - if (sVerbose) Log.v(TAG, "client is visible, check tracked ids"); - isVisible = client.autofillClientGetViewVisibility(trackedIds); - } else { - // All false - isVisible = new boolean[trackedIds.length]; - } - - final int numIds = trackedIds.length; - for (int i = 0; i < numIds; i++) { - final AutofillId id = trackedIds[i]; - id.resetSessionId(); + TrackedViews(@Nullable AutofillId[] trackedIds, @Nullable AutofillId[] allTrackedIds) { + mVisibleTrackedIds = new ArraySet<>(); + mInvisibleTrackedIds = new ArraySet<>(); + if (!ArrayUtils.isEmpty(trackedIds)) { + mIsTrackedSaveView = true; + initialTrackedViews(trackedIds, mVisibleTrackedIds, mInvisibleTrackedIds); + } - if (isVisible[i]) { - mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); - } else { - mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); - } - } + mVisibleDialogTrackedIds = new ArraySet<>(); + mInvisibleDialogTrackedIds = new ArraySet<>(); + if (!ArrayUtils.isEmpty(allTrackedIds)) { + initialTrackedViews(allTrackedIds, mVisibleDialogTrackedIds, + mInvisibleDialogTrackedIds); + mAllTrackedViews.addAll(Arrays.asList(allTrackedIds)); } if (sVerbose) { Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): " + " mVisibleTrackedIds=" + mVisibleTrackedIds - + " mInvisibleTrackedIds=" + mInvisibleTrackedIds); + + " mInvisibleTrackedIds=" + mInvisibleTrackedIds + + " allTrackedIds=" + Arrays.toString(allTrackedIds) + + " mVisibleDialogTrackedIds=" + mVisibleDialogTrackedIds + + " mInvisibleDialogTrackedIds=" + mInvisibleDialogTrackedIds); } - if (mVisibleTrackedIds == null) { + if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); } } + private void initialTrackedViews(AutofillId[] trackedIds, + @NonNull ArraySet<AutofillId> visibleSet, + @NonNull ArraySet<AutofillId> invisibleSet) { + final boolean[] isVisible; + final AutofillClient client = getClient(); + if (ArrayUtils.isEmpty(trackedIds) || client == null) { + return; + } + if (client.autofillClientIsVisibleForAutofill()) { + if (sVerbose) Log.v(TAG, "client is visible, check tracked ids"); + isVisible = client.autofillClientGetViewVisibility(trackedIds); + } else { + // All false + isVisible = new boolean[trackedIds.length]; + } + + final int numIds = trackedIds.length; + for (int i = 0; i < numIds; i++) { + final AutofillId id = trackedIds[i]; + id.resetSessionId(); + + if (isVisible[i]) { + addToSet(visibleSet, id); + } else { + addToSet(invisibleSet, id); + } + } + } + /** * Called when a {@link View view's} visibility changes. * @@ -3698,22 +3765,37 @@ public final class AutofillManager { if (isClientVisibleForAutofillLocked()) { if (isVisible) { if (isInSet(mInvisibleTrackedIds, id)) { - mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id); - mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id); + removeFromSet(mInvisibleTrackedIds, id); + addToSet(mVisibleTrackedIds, id); + } + if (isInSet(mInvisibleDialogTrackedIds, id)) { + removeFromSet(mInvisibleDialogTrackedIds, id); + addToSet(mVisibleDialogTrackedIds, id); } } else { if (isInSet(mVisibleTrackedIds, id)) { - mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id); - mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id); + removeFromSet(mVisibleTrackedIds, id); + addToSet(mInvisibleTrackedIds, id); + } + if (isInSet(mVisibleDialogTrackedIds, id)) { + removeFromSet(mVisibleDialogTrackedIds, id); + addToSet(mInvisibleDialogTrackedIds, id); } } } - if (mVisibleTrackedIds == null) { + if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { if (sVerbose) { Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds); } finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); + + } + if (mVisibleDialogTrackedIds.isEmpty()) { + if (sVerbose) { + Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds); + } + processNoVisibleTrackedAllViews(); } } @@ -3727,66 +3809,66 @@ public final class AutofillManager { // The visibility of the views might have changed while the client was not be visible, // hence update the visibility state for all views. AutofillClient client = getClient(); - ArraySet<AutofillId> updatedVisibleTrackedIds = null; - ArraySet<AutofillId> updatedInvisibleTrackedIds = null; if (client != null) { if (sVerbose) { Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds + " vis=" + mVisibleTrackedIds); } - if (mInvisibleTrackedIds != null) { - final ArrayList<AutofillId> orderedInvisibleIds = - new ArrayList<>(mInvisibleTrackedIds); - final boolean[] isVisible = client.autofillClientGetViewVisibility( - Helper.toArray(orderedInvisibleIds)); - - final int numInvisibleTrackedIds = orderedInvisibleIds.size(); - for (int i = 0; i < numInvisibleTrackedIds; i++) { - final AutofillId id = orderedInvisibleIds.get(i); - if (isVisible[i]) { - updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); - if (sDebug) { - Log.d(TAG, "onVisibleForAutofill() " + id + " became visible"); - } - } else { - updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); - } - } - } + onVisibleForAutofillChangedInternalLocked(mVisibleTrackedIds, mInvisibleTrackedIds); + onVisibleForAutofillChangedInternalLocked( + mVisibleDialogTrackedIds, mInvisibleDialogTrackedIds); + } - if (mVisibleTrackedIds != null) { - final ArrayList<AutofillId> orderedVisibleIds = - new ArrayList<>(mVisibleTrackedIds); - final boolean[] isVisible = client.autofillClientGetViewVisibility( - Helper.toArray(orderedVisibleIds)); + if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) { + if (sVerbose) { + Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); + } + finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); + } + if (mVisibleDialogTrackedIds.isEmpty()) { + if (sVerbose) { + Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); + } + processNoVisibleTrackedAllViews(); + } + } - final int numVisibleTrackedIds = orderedVisibleIds.size(); - for (int i = 0; i < numVisibleTrackedIds; i++) { - final AutofillId id = orderedVisibleIds.get(i); + void onVisibleForAutofillChangedInternalLocked(@NonNull ArraySet<AutofillId> visibleSet, + @NonNull ArraySet<AutofillId> invisibleSet) { + // The visibility of the views might have changed while the client was not be visible, + // hence update the visibility state for all views. + if (sVerbose) { + Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + invisibleSet + + " vis=" + visibleSet); + } - if (isVisible[i]) { - updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id); - } else { - updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id); + ArraySet<AutofillId> allTrackedIds = new ArraySet<>(); + allTrackedIds.addAll(visibleSet); + allTrackedIds.addAll(invisibleSet); + if (!allTrackedIds.isEmpty()) { + visibleSet.clear(); + invisibleSet.clear(); + initialTrackedViews(Helper.toArray(allTrackedIds), visibleSet, invisibleSet); + } + } - if (sDebug) { - Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible"); - } - } - } - } + private void processNoVisibleTrackedAllViews() { + mShowAutofillDialogCalled = false; + } - mInvisibleTrackedIds = updatedInvisibleTrackedIds; - mVisibleTrackedIds = updatedVisibleTrackedIds; + void checkViewState(AutofillId id) { + if (mAllTrackedViews.contains(id)) { + return; } - - if (mVisibleTrackedIds == null) { - if (sVerbose) { - Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids"); - } - finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED); + // Add the id as tracked to avoid triggering fill request again and again. + mAllTrackedViews.add(id); + if (mHasNewTrackedView) { + return; } + // First one new tracks view + mIsFillRequested.set(false); + mHasNewTrackedView = true; } } diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java index 1664637eac56..d067d4bc366b 100644 --- a/core/java/android/view/contentcapture/ContentCaptureManager.java +++ b/core/java/android/view/contentcapture/ContentCaptureManager.java @@ -378,7 +378,7 @@ public final class ContentCaptureManager { private final Object mLock = new Object(); @NonNull - private final Context mContext; + private final StrippedContext mContext; @NonNull private final IContentCaptureManager mService; @@ -414,9 +414,37 @@ public final class ContentCaptureManager { } /** @hide */ + static class StrippedContext { + final String mPackageName; + final String mContext; + final @UserIdInt int mUserId; + + private StrippedContext(Context context) { + mPackageName = context.getPackageName(); + mContext = context.toString(); + mUserId = context.getUserId(); + } + + @Override + public String toString() { + return mContext; + } + + public String getPackageName() { + return mPackageName; + } + + @UserIdInt + public int getUserId() { + return mUserId; + } + } + + /** @hide */ public ContentCaptureManager(@NonNull Context context, @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) { - mContext = Objects.requireNonNull(context, "context cannot be null"); + Objects.requireNonNull(context, "context cannot be null"); + mContext = new StrippedContext(context); mService = Objects.requireNonNull(service, "service cannot be null"); mOptions = Objects.requireNonNull(options, "options cannot be null"); diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java index c32ca9e2e215..a98955862d7b 100644 --- a/core/java/android/view/contentcapture/MainContentCaptureSession.java +++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java @@ -36,7 +36,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; import android.content.ComponentName; -import android.content.Context; import android.content.pm.ParceledListSlice; import android.graphics.Insets; import android.graphics.Rect; @@ -103,7 +102,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { private final AtomicBoolean mDisabled = new AtomicBoolean(false); @NonNull - private final Context mContext; + private final ContentCaptureManager.StrippedContext mContext; @NonNull private final ContentCaptureManager mManager; @@ -197,7 +196,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession { } } - protected MainContentCaptureSession(@NonNull Context context, + protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context, @NonNull ContentCaptureManager manager, @NonNull Handler handler, @NonNull IContentCaptureManager systemServerInterface) { mContext = context; diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java index a8ed96eb383d..2d974dbc8b6e 100644 --- a/core/java/android/view/inputmethod/CursorAnchorInfo.java +++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java @@ -20,12 +20,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Matrix; import android.graphics.RectF; +import android.inputmethodservice.InputMethodService; import android.os.Parcel; import android.os.Parcelable; import android.text.Layout; import android.text.SpannedString; import android.text.TextUtils; import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder; +import android.widget.TextView; import java.util.ArrayList; import java.util.Arrays; @@ -110,7 +112,7 @@ public final class CursorAnchorInfo implements Parcelable { /** * Container of rectangular position of Editor in the local coordinates that will be transformed * with the transformation matrix when rendered on the screen. - * @see {@link EditorBoundsInfo}. + * @see EditorBoundsInfo */ private final EditorBoundsInfo mEditorBoundsInfo; @@ -122,6 +124,12 @@ public final class CursorAnchorInfo implements Parcelable { private final float[] mMatrixValues; /** + * Information about text appearance in the editor for use by {@link InputMethodService}. + */ + @Nullable + private final TextAppearanceInfo mTextAppearanceInfo; + + /** * A list of visible line bounds stored in a float array. This array is divided into segment of * four where each element in the segment represents left, top, right respectively and bottom * of the line bounds. @@ -157,10 +165,11 @@ public final class CursorAnchorInfo implements Parcelable { mInsertionMarkerTop = source.readFloat(); mInsertionMarkerBaseline = source.readFloat(); mInsertionMarkerBottom = source.readFloat(); - mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class); + mCharacterBoundsArray = source.readTypedObject(SparseRectFArray.CREATOR); mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR); mMatrixValues = source.createFloatArray(); mVisibleLineBounds = source.createFloatArray(); + mTextAppearanceInfo = source.readTypedObject(TextAppearanceInfo.CREATOR); } /** @@ -181,10 +190,11 @@ public final class CursorAnchorInfo implements Parcelable { dest.writeFloat(mInsertionMarkerTop); dest.writeFloat(mInsertionMarkerBaseline); dest.writeFloat(mInsertionMarkerBottom); - dest.writeParcelable(mCharacterBoundsArray, flags); + dest.writeTypedObject(mCharacterBoundsArray, flags); dest.writeTypedObject(mEditorBoundsInfo, flags); dest.writeFloatArray(mMatrixValues); dest.writeFloatArray(mVisibleLineBounds); + dest.writeTypedObject(mTextAppearanceInfo, flags); } @Override @@ -262,6 +272,11 @@ public final class CursorAnchorInfo implements Parcelable { return false; } } + + if (!Objects.equals(mTextAppearanceInfo, that.mTextAppearanceInfo)) { + return false; + } + return true; } @@ -270,16 +285,17 @@ public final class CursorAnchorInfo implements Parcelable { return "CursorAnchorInfo{mHashCode=" + mHashCode + " mSelection=" + mSelectionStart + "," + mSelectionEnd + " mComposingTextStart=" + mComposingTextStart - + " mComposingText=" + Objects.toString(mComposingText) + + " mComposingText=" + mComposingText + " mInsertionMarkerFlags=" + mInsertionMarkerFlags + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal + " mInsertionMarkerTop=" + mInsertionMarkerTop + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline + " mInsertionMarkerBottom=" + mInsertionMarkerBottom - + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray) + + " mCharacterBoundsArray=" + mCharacterBoundsArray + " mEditorBoundsInfo=" + mEditorBoundsInfo + " mVisibleLineBounds=" + getVisibleLineBounds() + " mMatrix=" + Arrays.toString(mMatrixValues) + + " mTextAppearanceInfo=" + mTextAppearanceInfo + "}"; } @@ -303,6 +319,7 @@ public final class CursorAnchorInfo implements Parcelable { private boolean mMatrixInitialized = false; private float[] mVisibleLineBounds = new float[LINE_BOUNDS_INITIAL_SIZE * 4]; private int mVisibleLineBoundsCount = 0; + private TextAppearanceInfo mTextAppearanceInfo = null; /** * Sets the text range of the selection. Calling this can be skipped if there is no @@ -416,6 +433,17 @@ public final class CursorAnchorInfo implements Parcelable { } /** + * Set the information related to text appearance, which is extracted from the original + * {@link TextView}. + * @param textAppearanceInfo {@link TextAppearanceInfo} of TextView. + */ + @NonNull + public Builder setTextAppearanceInfo(@Nullable TextAppearanceInfo textAppearanceInfo) { + mTextAppearanceInfo = textAppearanceInfo; + return this; + } + + /** * Add the bounds of a visible text line of the current editor. * * The line bounds should not include the vertical space between lines or the horizontal @@ -504,6 +532,7 @@ public final class CursorAnchorInfo implements Parcelable { } mEditorBoundsInfo = null; clearVisibleLineBounds(); + mTextAppearanceInfo = null; } } @@ -524,7 +553,8 @@ public final class CursorAnchorInfo implements Parcelable { builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop, builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom, characterBoundsArray, builder.mEditorBoundsInfo, matrixValues, - Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount)); + Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount), + builder.mTextAppearanceInfo); } private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart, @@ -533,7 +563,8 @@ public final class CursorAnchorInfo implements Parcelable { float insertionMarkerBaseline, float insertionMarkerBottom, @Nullable SparseRectFArray characterBoundsArray, @Nullable EditorBoundsInfo editorBoundsInfo, - @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds) { + @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds, + @Nullable TextAppearanceInfo textAppearanceInfo) { mSelectionStart = selectionStart; mSelectionEnd = selectionEnd; mComposingTextStart = composingTextStart; @@ -547,6 +578,7 @@ public final class CursorAnchorInfo implements Parcelable { mEditorBoundsInfo = editorBoundsInfo; mMatrixValues = matrixValues; mVisibleLineBounds = visibleLineBounds; + mTextAppearanceInfo = textAppearanceInfo; // To keep hash function simple, we only use some complex objects for hash. int hashCode = Objects.hashCode(mComposingText); @@ -573,7 +605,7 @@ public final class CursorAnchorInfo implements Parcelable { original.mInsertionMarkerTop, original.mInsertionMarkerBaseline, original.mInsertionMarkerBottom, original.mCharacterBoundsArray, original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original), - original.mVisibleLineBounds); + original.mVisibleLineBounds, original.mTextAppearanceInfo); } /** @@ -741,6 +773,16 @@ public final class CursorAnchorInfo implements Parcelable { } /** + * Returns {@link TextAppearanceInfo} for the current editor, or {@code null} if IME is not + * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_TEXT_APPEARANCE} + * or {@link InputConnection#CURSOR_UPDATE_MONITOR}. + */ + @Nullable + public TextAppearanceInfo getTextAppearanceInfo() { + return mTextAppearanceInfo; + } + + /** * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation * matrix that is to be applied other positional data in this class. * @return a new instance (copy) of the transformation matrix. diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 7d268a925f60..2099a31c3e11 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -16,11 +16,14 @@ package android.view.inputmethod; +import static android.view.inputmethod.TextBoundsInfoResult.CODE_UNSUPPORTED; + import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.RectF; import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.Handler; @@ -32,7 +35,9 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -1052,8 +1057,10 @@ public interface InputConnection { * used together with {@link #CURSOR_UPDATE_MONITOR}. * <p> * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, - * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS} and - * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} are included but specifying them can + * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can * filter-out others. * It can be CPU intensive to include all, filtering specific info is recommended. * </p> @@ -1071,7 +1078,8 @@ public interface InputConnection { * <p> * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, - * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and + * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can * filter-out others. * It can be CPU intensive to include all, filtering specific info is recommended. @@ -1087,6 +1095,7 @@ public interface InputConnection { * <p> * This flag can be used together with filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. * </p> @@ -1102,8 +1111,8 @@ public interface InputConnection { * <p> * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS}, - * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags - * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. + * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} + * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. * </p> */ int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 1 << 3; @@ -1118,8 +1127,8 @@ public interface InputConnection { * <p> * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS}, - * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} and update flags {@link #CURSOR_UPDATE_IMMEDIATE} - * and {@link #CURSOR_UPDATE_MONITOR}. + * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} + * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. * </p> */ int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4; @@ -1133,14 +1142,29 @@ public interface InputConnection { * {@link InputConnection#requestCursorUpdates(int)} again with this flag off. * <p> * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, - * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} - * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} - * and {@link #CURSOR_UPDATE_MONITOR}. + * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, + * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE} and update flags + * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. * </p> */ int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 1 << 5; /** + * The editor is requested to call + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} + * with new text appearance info {@link CursorAnchorInfo#getTextAppearanceInfo()}} + * whenever cursor/anchor position is changed. To disable monitoring, call + * {@link InputConnection#requestCursorUpdates(int)} again with this flag off. + * <p> + * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, + * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and update flags + * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. + * </p> + */ + int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 1 << 6; + + /** * @hide */ @Retention(RetentionPolicy.SOURCE) @@ -1153,7 +1177,8 @@ public interface InputConnection { */ @Retention(RetentionPolicy.SOURCE) @IntDef(value = {CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS, - CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS}, + CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS, + CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, flag = true, prefix = { "CURSOR_UPDATE_FILTER_" }) @interface CursorUpdateFilter{} @@ -1205,6 +1230,40 @@ public interface InputConnection { return false; } + + /** + * Called by input method to request the {@link TextBoundsInfo} for a range of text which is + * covered by or in vicinity of the given {@code RectF}. It can be used as a supplementary + * method to implement the handwriting gesture API - + * {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}. + * + * <p><strong>Editor authors</strong>: It's preferred that the editor returns a + * {@link TextBoundsInfo} of all the text lines whose bounds intersect with the given + * {@code rectF}. + * </p> + * + * <p><strong>IME authors</strong>: This method is expensive when the text is long. Please + * consider that both the text bounds computation and IPC round-trip to send the data are time + * consuming. It's preferable to only request text bounds in smaller areas. + * </p> + * + * @param rectF the interested area where the text bounds are requested, in the screen + * coordinates. + * @param executor the executor to run the callback. + * @param consumer the callback invoked by editor to return the result. It must return a + * non-null object. + * + * @see TextBoundsInfo + * @see android.view.inputmethod.TextBoundsInfoResult + */ + default void requestTextBoundsInfo( + @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<TextBoundsInfoResult> consumer) { + Objects.requireNonNull(executor); + Objects.requireNonNull(consumer); + executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_UNSUPPORTED))); + } + /** * Called by the system to enable application developers to specify a dedicated thread on which * {@link InputConnection} methods are called back. diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 56beddf2ef38..7af96b60938c 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.RectF; import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; @@ -27,6 +28,7 @@ import android.view.KeyEvent; import com.android.internal.util.Preconditions; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -347,6 +349,17 @@ public class InputConnectionWrapper implements InputConnection { * @throws NullPointerException if the target is {@code null}. */ @Override + public void requestTextBoundsInfo( + @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<TextBoundsInfoResult> consumer) { + mTarget.requestTextBoundsInfo(rectF, executor, consumer); + } + + /** + * {@inheritDoc} + * @throws NullPointerException if the target is {@code null}. + */ + @Override public Handler getHandler() { return mTarget.getHandler(); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index d5292dbfb4df..d08816b7dafc 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -126,6 +126,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; /** @@ -1915,8 +1916,11 @@ public final class InputMethodManager { * a result receiver: explicitly request that the current input method's * soft input area be shown to the user, if needed. * - * @param view The currently focused view, which would like to receive - * soft keyboard input. + * @param view The currently focused view, which would like to receive soft keyboard input. + * Note that this view is only considered focused here if both it itself has + * {@link View#isFocused view focus}, and its containing window has + * {@link View#hasWindowFocus window focus}. Otherwise the call fails and + * returns {@code false}. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} bit set. */ @@ -1978,8 +1982,11 @@ public final class InputMethodManager { * can be garbage collected regardless of the lifetime of * {@link ResultReceiver}. * - * @param view The currently focused view, which would like to receive - * soft keyboard input. + * @param view The currently focused view, which would like to receive soft keyboard input. + * Note that this view is only considered focused here if both it itself has + * {@link View#isFocused view focus}, and its containing window has + * {@link View#hasWindowFocus window focus}. Otherwise the call fails and + * returns {@code false}. * @param flags Provides additional operating flags. Currently may be * 0 or have the {@link #SHOW_IMPLICIT} bit set. * @param resultReceiver If non-null, this will be called by the IME when @@ -3633,6 +3640,32 @@ public final class InputMethodManager { } /** + * {@code true} means that + * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns + * {@code false} when the IME client and the IME run in different displays. + */ + final AtomicBoolean mRequestCursorUpdateDisplayIdCheck = new AtomicBoolean(true); + + /** + * Controls the display ID mismatch validation in + * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)}. + * + * <p>{@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} is not guaranteed to work + * correctly when the IME client and the IME run in different displays. This is why + * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns + * {@code false} by default when the display ID does not match. This method allows special apps + * to override this behavior when they are sure that it should work.</p> + * + * <p>By default the validation is enabled.</p> + * + * @param enabled {@code false} to disable the display ID validation. + * @hide + */ + public void setRequestCursorUpdateDisplayIdCheck(boolean enabled) { + mRequestCursorUpdateDisplayIdCheck.set(enabled); + } + + /** * An internal API for {@link android.hardware.display.VirtualDisplay} to report where its * embedded virtual display is placed. * diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java index 9f0328909190..0449a16d785d 100644 --- a/core/java/android/view/inputmethod/InsertGesture.java +++ b/core/java/android/view/inputmethod/InsertGesture.java @@ -21,7 +21,6 @@ import android.annotation.SuppressLint; import android.graphics.PointF; import android.os.Parcel; import android.os.Parcelable; -import android.text.TextUtils; import android.widget.TextView; import androidx.annotation.Nullable; @@ -52,7 +51,8 @@ public final class InsertGesture extends HandwritingGesture implements Parcelabl mPoint = source.readTypedObject(PointF.CREATOR); } - /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/ + /** Returns the text that will be inserted at {@link #getInsertionPoint()}. When text is + * empty, cursor should be moved the insertion point. **/ @NonNull public String getTextToInsert() { return mTextToInsert; @@ -75,7 +75,11 @@ public final class InsertGesture extends HandwritingGesture implements Parcelabl private PointF mPoint; private String mFallbackText; - /** set the text that will be inserted at {@link #setInsertionPoint(PointF)} **/ + /** + * Set the text that will be inserted at {@link #setInsertionPoint(PointF)}. When set with + * an empty string, cursor will be moved to {@link #getInsertionPoint()} and no text + * would be inserted. + */ @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setTextToInsert(@NonNull String text) { @@ -114,8 +118,8 @@ public final class InsertGesture extends HandwritingGesture implements Parcelabl if (mPoint == null) { throw new IllegalArgumentException("Insertion point must be set."); } - if (TextUtils.isEmpty(mText)) { - throw new IllegalArgumentException("Text to insert must be non-empty."); + if (mText == null) { + throw new IllegalArgumentException("Text to insert must be set."); } return new InsertGesture(mText, mPoint, mFallbackText); } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl index 1550ab3bed63..ffadf820325e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl +++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 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,6 +14,6 @@ * limitations under the License. */ -package com.android.systemui.shared.system; +package android.view.inputmethod; -parcelable RemoteTransitionCompat; +parcelable ParcelableHandwritingGesture; diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java new file mode 100644 index 000000000000..e4066fcfd61d --- /dev/null +++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A generic container of parcelable {@link HandwritingGesture}. + * + * @hide + */ +public final class ParcelableHandwritingGesture implements Parcelable { + @NonNull + private final HandwritingGesture mGesture; + @NonNull + private final Parcelable mGestureAsParcelable; + + private ParcelableHandwritingGesture(@NonNull HandwritingGesture gesture) { + mGesture = gesture; + // For fail-fast. + mGestureAsParcelable = (Parcelable) gesture; + } + + /** + * Creates {@link ParcelableHandwritingGesture} from {@link HandwritingGesture}, which also + * implements {@link Parcelable}. + * + * @param gesture {@link HandwritingGesture} object to be stored. + * @return {@link ParcelableHandwritingGesture} to be stored in {@link Parcel}. + */ + @NonNull + public static ParcelableHandwritingGesture of(@NonNull HandwritingGesture gesture) { + return new ParcelableHandwritingGesture(Objects.requireNonNull(gesture)); + } + + /** + * @return {@link HandwritingGesture} object stored in this container. + */ + @NonNull + public HandwritingGesture get() { + return mGesture; + } + + private static HandwritingGesture createFromParcelInternal( + @HandwritingGesture.GestureType int gestureType, @NonNull Parcel parcel) { + switch (gestureType) { + case HandwritingGesture.GESTURE_TYPE_NONE: + throw new UnsupportedOperationException("GESTURE_TYPE_NONE is not supported"); + case HandwritingGesture.GESTURE_TYPE_SELECT: + return SelectGesture.CREATOR.createFromParcel(parcel); + case HandwritingGesture.GESTURE_TYPE_SELECT_RANGE: + return SelectRangeGesture.CREATOR.createFromParcel(parcel); + case HandwritingGesture.GESTURE_TYPE_INSERT: + return InsertGesture.CREATOR.createFromParcel(parcel); + case HandwritingGesture.GESTURE_TYPE_DELETE: + return DeleteGesture.CREATOR.createFromParcel(parcel); + case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE: + return DeleteRangeGesture.CREATOR.createFromParcel(parcel); + case HandwritingGesture.GESTURE_TYPE_JOIN_OR_SPLIT: + return JoinOrSplitGesture.CREATOR.createFromParcel(parcel); + case HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE: + return RemoveSpaceGesture.CREATOR.createFromParcel(parcel); + default: + throw new UnsupportedOperationException("Unknown type=" + gestureType); + } + } + + public static final Creator<ParcelableHandwritingGesture> CREATOR = new Parcelable.Creator<>() { + @Override + public ParcelableHandwritingGesture createFromParcel(Parcel in) { + final int gestureType = in.readInt(); + return new ParcelableHandwritingGesture(createFromParcelInternal(gestureType, in)); + } + + @Override + public ParcelableHandwritingGesture[] newArray(int size) { + return new ParcelableHandwritingGesture[size]; + } + }; + + @Override + public int describeContents() { + return mGestureAsParcelable.describeContents(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mGesture.getGestureType()); + mGestureAsParcelable.writeToParcel(dest, flags); + } +} diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index f2b70997de63..a9277658aaa7 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -28,6 +28,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.RectF; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -982,62 +983,9 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Dispatching(cancellable = true) @Override - public void performHandwritingSelectGesture( - InputConnectionCommandHeader header, SelectGesture gesture, + public void performHandwritingGesture( + InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer, ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - @Dispatching(cancellable = true) - @Override - public void performHandwritingSelectRangeGesture( - InputConnectionCommandHeader header, SelectRangeGesture gesture, - ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - @Dispatching(cancellable = true) - @Override - public void performHandwritingInsertGesture( - InputConnectionCommandHeader header, InsertGesture gesture, - ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - @Dispatching(cancellable = true) - @Override - public void performHandwritingDeleteGesture( - InputConnectionCommandHeader header, DeleteGesture gesture, - ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - @Dispatching(cancellable = true) - @Override - public void performHandwritingDeleteRangeGesture( - InputConnectionCommandHeader header, DeleteRangeGesture gesture, - ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - @Dispatching(cancellable = true) - @Override - public void performHandwritingRemoveSpaceGesture( - InputConnectionCommandHeader header, RemoveSpaceGesture gesture, - ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - @Dispatching(cancellable = true) - @Override - public void performHandwritingJoinOrSplitGesture( - InputConnectionCommandHeader header, JoinOrSplitGesture gesture, - ResultReceiver resultReceiver) { - performHandwritingGestureInternal(header, gesture, resultReceiver); - } - - private <T extends HandwritingGesture> void performHandwritingGestureInternal( - InputConnectionCommandHeader header, T gesture, ResultReceiver resultReceiver) { dispatchWithTracing("performHandwritingGesture", () -> { if (header.mSessionId != mCurrentSessionId.get()) { if (resultReceiver != null) { @@ -1059,7 +1007,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { // TODO(210039666): implement Cleaner to return HANDWRITING_GESTURE_RESULT_UNKNOWN if // editor doesn't return any type. ic.performHandwritingGesture( - gesture, + gestureContainer.get(), resultReceiver != null ? Runnable::run : null, resultReceiver != null ? (resultCode) -> resultReceiver.send(resultCode, null /* resultData */) @@ -1123,7 +1071,8 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); return false; } - if (mParentInputMethodManager.getDisplayId() != imeDisplayId + if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get() + && mParentInputMethodManager.getDisplayId() != imeDisplayId && !mParentInputMethodManager.hasVirtualDisplayToScreenMatrix()) { // requestCursorUpdates() is not currently supported across displays. return false; @@ -1147,6 +1096,36 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Dispatching(cancellable = true) @Override + public void requestTextBoundsInfo( + InputConnectionCommandHeader header, RectF rectF, + @NonNull ResultReceiver resultReceiver) { + dispatchWithTracing("requestTextBoundsInfo", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null); + return; // cancelled + } + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection"); + resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null); + return; + } + + ic.requestTextBoundsInfo( + rectF, + Runnable::run, + (textBoundsInfoResult) -> { + final int resultCode = textBoundsInfoResult.getResultCode(); + final TextBoundsInfo textBoundsInfo = + textBoundsInfoResult.getTextBoundsInfo(); + resultReceiver.send(resultCode, + textBoundsInfo == null ? null : textBoundsInfo.toBundle()); + }); + }); + } + + @Dispatching(cancellable = true) + @Override public void commitContent(InputConnectionCommandHeader header, InputContentInfo inputContentInfo, int flags, Bundle opts, AndroidFuture future /* T=Boolean */) { diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java new file mode 100644 index 000000000000..1df4fc54089e --- /dev/null +++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.ColorInt; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.Px; +import android.content.res.ColorStateList; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.graphics.fonts.FontStyle; +import android.graphics.text.LineBreakConfig; +import android.inputmethodservice.InputMethodService; +import android.os.LocaleList; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.InputFilter; +import android.widget.TextView; + +import java.util.Objects; + +/** + * Information about text appearance in an editor, passed through + * {@link CursorAnchorInfo} for use by {@link InputMethodService}. + * + * @see TextView + * @see Paint + * @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo) + * @see CursorAnchorInfo#getTextAppearanceInfo() + */ +public final class TextAppearanceInfo implements Parcelable { + /** + * The text size (in pixels) for current {@link TextView}. + */ + private final @Px float mTextSize; + + /** + * The LocaleList of the text. + */ + @NonNull private final LocaleList mTextLocales; + + /** + * The font family name if the {@link Typeface} of the text is created from a system font + * family, otherwise this value should be null. + */ + @Nullable private final String mSystemFontFamilyName; + + /** + * The weight of the text. + */ + private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight; + + /** + * The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}. + */ + private final @Typeface.Style int mTextStyle; + + /** + * Whether the transformation method applied to the current {@link TextView} is set to + * ALL CAPS. + */ + private final boolean mAllCaps; + + /** + * The horizontal offset (in pixels) of the text shadow. + */ + private final @Px float mShadowDx; + + /** + * The vertical offset (in pixels) of the text shadow. + */ + private final @Px float mShadowDy; + + /** + * The blur radius (in pixels) of the text shadow. + */ + private final @Px float mShadowRadius; + + /** + * The elegant text height, especially for less compacted complex script text. + */ + private final boolean mElegantTextHeight; + + /** + * Whether to expand linespacing based on fallback fonts. + */ + private final boolean mFallbackLineSpacing; + + /** + * The text letter-spacing (in ems), which determines the spacing between characters. + */ + private final float mLetterSpacing; + + /** + * The font feature settings. + */ + @Nullable private final String mFontFeatureSettings; + + /** + * The font variation settings. + */ + @Nullable private final String mFontVariationSettings; + + /** + * The line-break strategies for text wrapping. + */ + private final @LineBreakConfig.LineBreakStyle int mLineBreakStyle; + + /** + * The line-break word strategies for text wrapping. + */ + private final @LineBreakConfig.LineBreakWordStyle int mLineBreakWordStyle; + + /** + * The extent by which text should be stretched horizontally. Returns 1.0 if not specified. + */ + private final float mTextScaleX; + + /** + * The color of the text selection highlight. + */ + private final @ColorInt int mTextColorHighlight; + + /** + * The current text color. + */ + private final @ColorInt int mTextColor; + + /** + * The current color of the hint text. + */ + private final @ColorInt int mTextColorHint; + + /** + * The text color for links. + */ + @Nullable private final ColorStateList mTextColorLink; + + /** + * The max length of text. + */ + private final int mMaxLength; + + + public TextAppearanceInfo(@NonNull TextView textView) { + mTextSize = textView.getTextSize(); + mTextLocales = textView.getTextLocales(); + Typeface typeface = textView.getPaint().getTypeface(); + String systemFontFamilyName = null; + int textFontWeight = -1; + if (typeface != null) { + systemFontFamilyName = typeface.getSystemFontFamilyName(); + textFontWeight = typeface.getWeight(); + } + mSystemFontFamilyName = systemFontFamilyName; + mTextFontWeight = textFontWeight; + mTextStyle = textView.getTypefaceStyle(); + mAllCaps = textView.isAllCaps(); + mShadowRadius = textView.getShadowRadius(); + mShadowDx = textView.getShadowDx(); + mShadowDy = textView.getShadowDy(); + mElegantTextHeight = textView.isElegantTextHeight(); + mFallbackLineSpacing = textView.isFallbackLineSpacing(); + mLetterSpacing = textView.getLetterSpacing(); + mFontFeatureSettings = textView.getFontFeatureSettings(); + mFontVariationSettings = textView.getFontVariationSettings(); + mLineBreakStyle = textView.getLineBreakStyle(); + mLineBreakWordStyle = textView.getLineBreakWordStyle(); + mTextScaleX = textView.getTextScaleX(); + mTextColorHighlight = textView.getHighlightColor(); + mTextColor = textView.getCurrentTextColor(); + mTextColorHint = textView.getCurrentHintTextColor(); + mTextColorLink = textView.getLinkTextColors(); + int maxLength = -1; + for (InputFilter filter: textView.getFilters()) { + if (filter instanceof InputFilter.LengthFilter) { + maxLength = ((InputFilter.LengthFilter) filter).getMax(); + // There is at most one LengthFilter. + break; + } + } + mMaxLength = maxLength; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeFloat(mTextSize); + mTextLocales.writeToParcel(dest, flags); // NonNull + dest.writeBoolean(mAllCaps); + dest.writeString8(mSystemFontFamilyName); + dest.writeInt(mTextFontWeight); + dest.writeInt(mTextStyle); + dest.writeFloat(mShadowDx); + dest.writeFloat(mShadowDy); + dest.writeFloat(mShadowRadius); + dest.writeBoolean(mElegantTextHeight); + dest.writeBoolean(mFallbackLineSpacing); + dest.writeFloat(mLetterSpacing); + dest.writeString8(mFontFeatureSettings); + dest.writeString8(mFontVariationSettings); + dest.writeInt(mLineBreakStyle); + dest.writeInt(mLineBreakWordStyle); + dest.writeFloat(mTextScaleX); + dest.writeInt(mTextColorHighlight); + dest.writeInt(mTextColor); + dest.writeInt(mTextColorHint); + dest.writeTypedObject(mTextColorLink, flags); + dest.writeInt(mMaxLength); + } + + private TextAppearanceInfo(@NonNull Parcel in) { + mTextSize = in.readFloat(); + mTextLocales = LocaleList.CREATOR.createFromParcel(in); + mAllCaps = in.readBoolean(); + mSystemFontFamilyName = in.readString8(); + mTextFontWeight = in.readInt(); + mTextStyle = in.readInt(); + mShadowDx = in.readFloat(); + mShadowDy = in.readFloat(); + mShadowRadius = in.readFloat(); + mElegantTextHeight = in.readBoolean(); + mFallbackLineSpacing = in.readBoolean(); + mLetterSpacing = in.readFloat(); + mFontFeatureSettings = in.readString8(); + mFontVariationSettings = in.readString8(); + mLineBreakStyle = in.readInt(); + mLineBreakWordStyle = in.readInt(); + mTextScaleX = in.readFloat(); + mTextColorHighlight = in.readInt(); + mTextColor = in.readInt(); + mTextColorHint = in.readInt(); + mTextColorLink = in.readTypedObject(ColorStateList.CREATOR); + mMaxLength = in.readInt(); + } + + @NonNull + public static final Creator<TextAppearanceInfo> CREATOR = new Creator<TextAppearanceInfo>() { + @Override + public TextAppearanceInfo createFromParcel(@NonNull Parcel in) { + return new TextAppearanceInfo(in); + } + + @Override + public TextAppearanceInfo[] newArray(int size) { + return new TextAppearanceInfo[size]; + } + }; + + /** + * Returns the text size (in pixels) for current {@link TextView}. + */ + public @Px float getTextSize() { + return mTextSize; + } + + /** + * Returns the LocaleList of the text. + */ + @NonNull + public LocaleList getTextLocales() { + return mTextLocales; + } + + /** + * Returns the font family name if the {@link Typeface} of the text is created from a + * system font family. Returns null if no {@link Typeface} is specified, or it is not created + * from a system font family. + */ + @Nullable + public String getFontFamilyName() { + return mSystemFontFamilyName; + } + + /** + * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified. + */ + public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() { + return mTextFontWeight; + } + + /** + * Returns the style (normal, bold, italic, bold|italic) of the text. Returns + * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for + * more information. + */ + public @Typeface.Style int getTextStyle() { + return mTextStyle; + } + + /** + * Returns whether the transformation method applied to the current {@link TextView} is set to + * ALL CAPS. + */ + public boolean isAllCaps() { + return mAllCaps; + } + + /** + * Returns the horizontal offset (in pixels) of the text shadow. + */ + public @Px float getShadowDx() { + return mShadowDx; + } + + /** + * Returns the vertical offset (in pixels) of the text shadow. + */ + public @Px float getShadowDy() { + return mShadowDy; + } + + /** + * Returns the blur radius (in pixels) of the text shadow. + */ + public @Px float getShadowRadius() { + return mShadowRadius; + } + + /** + * Returns {@code true} if the elegant height metrics flag is set. This setting selects font + * variants that have not been compacted to fit Latin-based vertical metrics, and also increases + * top and bottom bounds to provide more space. + */ + public boolean isElegantTextHeight() { + return mElegantTextHeight; + } + + /** + * Returns whether to expand linespacing based on fallback fonts. + * + * @see TextView#setFallbackLineSpacing(boolean) + */ + public boolean isFallbackLineSpacing() { + return mFallbackLineSpacing; + } + + /** + * Returns the text letter-spacing, which determines the spacing between characters. + * The value is in 'EM' units. Normally, this value is 0.0. + */ + public float getLetterSpacing() { + return mLetterSpacing; + } + + /** + * Returns the font feature settings. Returns null if not specified. + * + * @see Paint#getFontFeatureSettings() + */ + @Nullable + public String getFontFeatureSettings() { + return mFontFeatureSettings; + } + + /** + * Returns the font variation settings. Returns null if no variation is specified. + * + * @see Paint#getFontVariationSettings() + */ + @Nullable + public String getFontVariationSettings() { + return mFontVariationSettings; + } + + /** + * Returns the line-break strategies for text wrapping. + * + * @see TextView#setLineBreakStyle(int) + */ + public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() { + return mLineBreakStyle; + } + + /** + * Returns the line-break word strategies for text wrapping. + * + * @see TextView#setLineBreakWordStyle(int) + */ + public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() { + return mLineBreakWordStyle; + } + + /** + * Returns the extent by which text should be stretched horizontally. Returns 1.0 if not + * specified. + */ + public float getTextScaleX() { + return mTextScaleX; + } + + /** + * Returns the color of the text selection highlight. + */ + public @ColorInt int getTextColorHighlight() { + return mTextColorHighlight; + } + + /** + * Returns the current text color. + */ + public @ColorInt int getTextColor() { + return mTextColor; + } + + /** + * Returns the current color of the hint text. + */ + public @ColorInt int getTextColorHint() { + return mTextColorHint; + } + + /** + * Returns the text color for links. + */ + @Nullable + public ColorStateList getTextColorLink() { + return mTextColorLink; + } + + /** + * Returns the max length of text, which is used to set an input filter to constrain the text + * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter} + * in the Editor. + */ + public int getMaxLength() { + return mMaxLength; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TextAppearanceInfo)) return false; + TextAppearanceInfo that = (TextAppearanceInfo) o; + return Float.compare(that.mTextSize, mTextSize) == 0 + && mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle + && mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0 + && Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare( + that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength + && mElegantTextHeight == that.mElegantTextHeight + && mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare( + that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle + && mLineBreakWordStyle == that.mLineBreakWordStyle + && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor + && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor() + && mTextColorHint == that.mTextColorHint && Objects.equals( + mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName, + that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings, + that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings, + that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight, + mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight, + mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings, + mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor, + mTextColorHint, mTextColorLink, mMaxLength); + } + + @Override + public String toString() { + return "TextAppearanceInfo{" + + "mTextSize=" + mTextSize + + ", mTextLocales=" + mTextLocales + + ", mSystemFontFamilyName='" + mSystemFontFamilyName + '\'' + + ", mTextFontWeight=" + mTextFontWeight + + ", mTextStyle=" + mTextStyle + + ", mAllCaps=" + mAllCaps + + ", mShadowDx=" + mShadowDx + + ", mShadowDy=" + mShadowDy + + ", mShadowRadius=" + mShadowRadius + + ", mElegantTextHeight=" + mElegantTextHeight + + ", mFallbackLineSpacing=" + mFallbackLineSpacing + + ", mLetterSpacing=" + mLetterSpacing + + ", mFontFeatureSettings='" + mFontFeatureSettings + '\'' + + ", mFontVariationSettings='" + mFontVariationSettings + '\'' + + ", mLineBreakStyle=" + mLineBreakStyle + + ", mLineBreakWordStyle=" + mLineBreakWordStyle + + ", mTextScaleX=" + mTextScaleX + + ", mTextColorHighlight=" + mTextColorHighlight + + ", mTextColor=" + mTextColor + + ", mTextColorHint=" + mTextColorHint + + ", mTextColorLink=" + mTextColorLink + + ", mMaxLength=" + mMaxLength + + '}'; + } +} diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java new file mode 100644 index 000000000000..4e87405f0137 --- /dev/null +++ b/core/java/android/view/inputmethod/TextBoundsInfo.java @@ -0,0 +1,844 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Matrix; +import android.graphics.RectF; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.SegmentFinder; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.GrowingArrayUtils; +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * The text bounds information of a slice of text in the editor. + * + * <p> This class provides IME the layout information of the text within the range from + * {@link #getStart()} to {@link #getEnd()}. It's intended to be used by IME as a supplementary API + * to support handwriting gestures. + * </p> + */ +public final class TextBoundsInfo implements Parcelable { + /** + * The flag indicating that the character is a whitespace. + * + * @see Builder#setCharacterFlags(int[]) + * @see #getCharacterFlags(int) + */ + public static final int FLAG_CHARACTER_WHITESPACE = 1; + + /** + * The flag indicating that the character is a linefeed character. + * + * @see Builder#setCharacterFlags(int[]) + * @see #getCharacterFlags(int) + */ + public static final int FLAG_CHARACTER_LINEFEED = 1 << 1; + + /** + * The flag indicating that the character is a punctuation. + * + * @see Builder#setCharacterFlags(int[]) + * @see #getCharacterFlags(int) + */ + public static final int FLAG_CHARACTER_PUNCTUATION = 1 << 2; + + /** + * The flag indicating that the line this character belongs to has RTL line direction. It's + * required that all characters in the same line must have the same direction. + * + * @see Builder#setCharacterFlags(int[]) + * @see #getCharacterFlags(int) + */ + public static final int FLAG_LINE_IS_RTL = 1 << 3; + + + /** @hide */ + @IntDef(prefix = "FLAG_", flag = true, value = { + FLAG_CHARACTER_WHITESPACE, + FLAG_CHARACTER_LINEFEED, + FLAG_CHARACTER_PUNCTUATION, + FLAG_LINE_IS_RTL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CharacterFlags {} + + /** All the valid flags. */ + private static final int KNOWN_CHARACTER_FLAGS = FLAG_CHARACTER_WHITESPACE + | FLAG_CHARACTER_LINEFEED | FLAG_CHARACTER_PUNCTUATION | FLAG_LINE_IS_RTL; + + /** + * The amount of shift to get the character's BiDi level from the internal character flags. + */ + private static final int BIDI_LEVEL_SHIFT = 19; + + /** + * The mask used to get the character's BiDi level from the internal character flags. + */ + private static final int BIDI_LEVEL_MASK = 0x7F << BIDI_LEVEL_SHIFT; + + /** + * The flag indicating that the character at the index is the start of a line segment. + * This flag is only used internally to serialize the {@link SegmentFinder}. + * + * @see #writeToParcel(Parcel, int) + */ + private static final int FLAG_LINE_SEGMENT_START = 1 << 31; + + /** + * The flag indicating that the character at the index is the end of a line segment. + * This flag is only used internally to serialize the {@link SegmentFinder}. + * + * @see #writeToParcel(Parcel, int) + */ + private static final int FLAG_LINE_SEGMENT_END = 1 << 30; + + /** + * The flag indicating that the character at the index is the start of a word segment. + * This flag is only used internally to serialize the {@link SegmentFinder}. + * + * @see #writeToParcel(Parcel, int) + */ + private static final int FLAG_WORD_SEGMENT_START = 1 << 29; + + /** + * The flag indicating that the character at the index is the end of a word segment. + * This flag is only used internally to serialize the {@link SegmentFinder}. + * + * @see #writeToParcel(Parcel, int) + */ + private static final int FLAG_WORD_SEGMENT_END = 1 << 28; + + /** + * The flag indicating that the character at the index is the start of a grapheme segment. + * It's only used internally to serialize the {@link SegmentFinder}. + * + * @see #writeToParcel(Parcel, int) + */ + private static final int FLAG_GRAPHEME_SEGMENT_START = 1 << 27; + + /** + * The flag indicating that the character at the index is the end of a grapheme segment. + * It's only used internally to serialize the {@link SegmentFinder}. + * + * @see #writeToParcel(Parcel, int) + */ + private static final int FLAG_GRAPHEME_SEGMENT_END = 1 << 26; + + private final int mStart; + private final int mEnd; + private final float[] mMatrixValues; + private final float[] mCharacterBounds; + /** + * The array that encodes character and BiDi levels. They are stored together to save memory + * space, and it's easier during serialization. + */ + private final int[] mInternalCharacterFlags; + private final SegmentFinder mLineSegmentFinder; + private final SegmentFinder mWordSegmentFinder; + private final SegmentFinder mGraphemeSegmentFinder; + + /** + * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation + * matrix that is to be applied other positional data in this class. + * + * @return a new instance (copy) of the transformation matrix. + */ + @NonNull + public Matrix getMatrix() { + final Matrix matrix = new Matrix(); + matrix.setValues(mMatrixValues); + return matrix; + } + + /** + * Returns the index of the first character whose bounds information is available in this + * {@link TextBoundsInfo}, inclusive. + * + * @see Builder#setStartAndEnd(int, int) + */ + public int getStart() { + return mStart; + } + + /** + * Returns the index of the last character whose bounds information is available in this + * {@link TextBoundsInfo}, exclusive. + * + * @see Builder#setStartAndEnd(int, int) + */ + public int getEnd() { + return mEnd; + } + + /** + * Return the bounds of the character at the given {@code index}, in the coordinates of the + * editor. + * + * @param index the index of the queried character. + * @return the bounding box of the queried character. + * + * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from + * the {@code start} to the {@code end}. + */ + @NonNull + public RectF getCharacterBounds(int index) { + if (index < mStart || index >= mEnd) { + throw new IndexOutOfBoundsException("Index is out of the bounds of " + + "[" + mStart + ", " + mEnd + ")."); + } + final int offset = 4 * (index - mStart); + return new RectF(mCharacterBounds[offset], mCharacterBounds[offset + 1], + mCharacterBounds[offset + 2], mCharacterBounds[offset + 3]); + } + + /** + * Return the flags associated with the character at the given {@code index}. + * The flags contain the following information: + * <ul> + * <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a + * whitespace. </li> + * <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a + * linefeed. </li> + * <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a + * punctuation. </li> + * <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to + * has RTL line direction. All characters in the same line must have the same line + * direction. Check {@link #getLineSegmentFinder()} for more information of + * line boundaries. </li> + * </ul> + * + * @param index the index of the queried character. + * @return the flags associated with the queried character. + * + * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from + * the {@code start} to the {@code end}. + * + * @see #FLAG_CHARACTER_WHITESPACE + * @see #FLAG_CHARACTER_LINEFEED + * @see #FLAG_CHARACTER_PUNCTUATION + * @see #FLAG_LINE_IS_RTL + */ + @CharacterFlags + public int getCharacterFlags(int index) { + if (index < mStart || index >= mEnd) { + throw new IndexOutOfBoundsException("Index is out of the bounds of " + + "[" + mStart + ", " + mEnd + ")."); + } + final int offset = index - mStart; + return mInternalCharacterFlags[offset] & KNOWN_CHARACTER_FLAGS; + } + + /** + * The BiDi level of the character at the given {@code index}. <br/> + * BiDi level is defined by + * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode + * bidirectional algorithm </a>. One can determine whether a character's direction is + * right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level. + * If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a + * character must be in the range of [0, 125]. + * + * @param index the index of the queried character. + * @return the BiDi level of the character, which is an integer in the range of [0, 125]. + * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from + * the {@code start} to the {@code end}. + * + * @see Builder#setCharacterBidiLevel(int[]) + */ + @IntRange(from = 0, to = 125) + public int getCharacterBidiLevel(int index) { + if (index < mStart || index >= mEnd) { + throw new IndexOutOfBoundsException("Index is out of the bounds of " + + "[" + mStart + ", " + mEnd + ")."); + } + final int offset = index - mStart; + return (mInternalCharacterFlags[offset] & BIDI_LEVEL_MASK) >> BIDI_LEVEL_SHIFT; + } + + /** + * Returns the {@link SegmentFinder} that locates the word boundaries. + * + * @see Builder#setWordSegmentFinder(SegmentFinder) + */ + @NonNull + public SegmentFinder getWordSegmentFinder() { + return mWordSegmentFinder; + } + + /** + * Returns the {@link SegmentFinder} that locates the grapheme boundaries. + * + * @see Builder#setGraphemeSegmentFinder(SegmentFinder) + */ + @NonNull + public SegmentFinder getGraphemeSegmentFinder() { + return mGraphemeSegmentFinder; + } + + /** + * Returns the {@link SegmentFinder} that locates the line boundaries. + * + * @see Builder#setLineSegmentFinder(SegmentFinder) + */ + @NonNull + public SegmentFinder getLineSegmentFinder() { + return mLineSegmentFinder; + } + + /** + * Describe the kinds of special objects contained in this Parcelable + * instance's marshaled representation. For example, if the object will + * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)}, + * the return value of this method must include the + * {@link #CONTENTS_FILE_DESCRIPTOR} bit. + * + * @return a bitmask indicating the set of special object types marshaled + * by this Parcelable object instance. + */ + @Override + public int describeContents() { + return 0; + } + + /** + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStart); + dest.writeInt(mEnd); + dest.writeFloatArray(mMatrixValues); + dest.writeFloatArray(mCharacterBounds); + + // The end can also be a break position. We need an extra space to encode the breaks. + final int[] encodedFlags = Arrays.copyOf(mInternalCharacterFlags, mEnd - mStart + 1); + encodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START, FLAG_GRAPHEME_SEGMENT_END, + mStart, mEnd, mGraphemeSegmentFinder); + encodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START, FLAG_WORD_SEGMENT_END, mStart, + mEnd, mWordSegmentFinder); + encodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START, FLAG_LINE_SEGMENT_END, mStart, + mEnd, mLineSegmentFinder); + dest.writeIntArray(encodedFlags); + } + + private TextBoundsInfo(Parcel source) { + mStart = source.readInt(); + mEnd = source.readInt(); + mMatrixValues = Objects.requireNonNull(source.createFloatArray()); + mCharacterBounds = Objects.requireNonNull(source.createFloatArray()); + final int[] encodedFlags = Objects.requireNonNull(source.createIntArray()); + + mGraphemeSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START, + FLAG_GRAPHEME_SEGMENT_END, mStart, mEnd); + mWordSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START, + FLAG_WORD_SEGMENT_END, mStart, mEnd); + mLineSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START, + FLAG_LINE_SEGMENT_END, mStart, mEnd); + + final int length = mEnd - mStart; + final int flagsMask = KNOWN_CHARACTER_FLAGS | BIDI_LEVEL_MASK; + mInternalCharacterFlags = new int[length]; + for (int i = 0; i < length; ++i) { + // Remove the flags used to encoded segment boundaries. + mInternalCharacterFlags[i] = encodedFlags[i] & flagsMask; + } + } + + private TextBoundsInfo(Builder builder) { + mStart = builder.mStart; + mEnd = builder.mEnd; + mMatrixValues = Arrays.copyOf(builder.mMatrixValues, 9); + final int length = mEnd - mStart; + mCharacterBounds = Arrays.copyOf(builder.mCharacterBounds, 4 * length); + // Store characterFlags and characterBidiLevels to save memory. + mInternalCharacterFlags = new int[length]; + for (int index = 0; index < length; ++index) { + mInternalCharacterFlags[index] = builder.mCharacterFlags[index] + | (builder.mCharacterBidiLevels[index] << BIDI_LEVEL_SHIFT); + } + mGraphemeSegmentFinder = builder.mGraphemeSegmentFinder; + mWordSegmentFinder = builder.mWordSegmentFinder; + mLineSegmentFinder = builder.mLineSegmentFinder; + } + + /** + * The CREATOR to make this class Parcelable. + */ + @NonNull + public static final Parcelable.Creator<TextBoundsInfo> CREATOR = new Creator<TextBoundsInfo>() { + @Override + public TextBoundsInfo createFromParcel(Parcel source) { + return new TextBoundsInfo(source); + } + + @Override + public TextBoundsInfo[] newArray(int size) { + return new TextBoundsInfo[size]; + } + }; + + private static final String TEXT_BOUNDS_INFO_KEY = "android.view.inputmethod.TextBoundsInfo"; + + /** + * Store the {@link TextBoundsInfo} into a {@link Bundle}. This method is used by + * {@link RemoteInputConnectionImpl} to transfer the {@link TextBoundsInfo} from the editor + * to IME. + * + * @see TextBoundsInfoResult + * @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer) + * @hide + */ + @NonNull + public Bundle toBundle() { + final Bundle bundle = new Bundle(); + bundle.putParcelable(TEXT_BOUNDS_INFO_KEY, this); + return bundle; + + } + + /** @hide */ + @Nullable + public static TextBoundsInfo createFromBundle(@Nullable Bundle bundle) { + if (bundle == null) return null; + return bundle.getParcelable(TEXT_BOUNDS_INFO_KEY, TextBoundsInfo.class); + } + + /** + * The builder class to create a {@link TextBoundsInfo} object. + */ + public static final class Builder { + private final float[] mMatrixValues = new float[9]; + private boolean mMatrixInitialized; + private int mStart; + private int mEnd; + private float[] mCharacterBounds; + private int[] mCharacterFlags; + private int[] mCharacterBidiLevels; + private SegmentFinder mLineSegmentFinder; + private SegmentFinder mWordSegmentFinder; + private SegmentFinder mGraphemeSegmentFinder; + + /** Clear all the parameters set on this {@link Builder} to reuse it. */ + @NonNull + public Builder clear() { + mMatrixInitialized = false; + mStart = -1; + mEnd = -1; + mCharacterBounds = null; + mCharacterFlags = null; + mLineSegmentFinder = null; + mWordSegmentFinder = null; + mGraphemeSegmentFinder = null; + return this; + } + + /** + * Sets the matrix that transforms local coordinates into screen coordinates. + * + * @param matrix transformation matrix from local coordinates into screen coordinates. + * @throws NullPointerException if the given {@code matrix} is {@code null}. + */ + @NonNull + public Builder setMatrix(@NonNull Matrix matrix) { + Objects.requireNonNull(matrix).getValues(mMatrixValues); + mMatrixInitialized = true; + return this; + } + + /** + * Set the start and end index of the {@link TextBoundsInfo}. It's the range of the + * characters whose information is available in the {@link TextBoundsInfo}. + * + * @param start the start index of the {@link TextBoundsInfo}, inclusive. + * @param end the end index of the {@link TextBoundsInfo}, exclusive. + * @throws IllegalArgumentException if the given {@code start} or {@code end} is negative, + * or {@code end} is smaller than the {@code start}. + */ + @NonNull + @SuppressWarnings("MissingGetterMatchingBuilder") + public Builder setStartAndEnd(@IntRange(from = 0) int start, @IntRange(from = 0) int end) { + Preconditions.checkArgument(start >= 0); + Preconditions.checkArgumentInRange(start, 0, end, "start"); + mStart = start; + mEnd = end; + return this; + } + + /** + * Set the characters bounds, in the coordinates of the editor. <br/> + * + * The given array should be divided into groups of four where each element represents + * left, top, right and bottom of the character bounds respectively. + * The bounds of the i-th character in the editor should be stored at index + * 4 * (i - start). The length of the given array must equal to 4 * (end - start). <br/> + * + * Sometimes multiple characters in a single grapheme are rendered as one symbol on the + * screen. So those characters only have one shared bounds. In this case, we recommend the + * editor to assign all the width to the bounds of the first character in the grapheme, + * and make the rest characters' bounds zero-width. <br/> + * + * For example, the string "'0xD83D' '0xDE00'" is rendered as one grapheme - a grinning face + * emoji. If the bounds of the grapheme is: Rect(5, 10, 15, 20), the character bounds of the + * string should be: [ Rect(5, 10, 15, 20), Rect(15, 10, 15, 20) ]. + * + * @param characterBounds the array of the flattened character bounds. + * @throws NullPointerException if the given {@code characterBounds} is {@code null}. + */ + @NonNull + public Builder setCharacterBounds(@NonNull float[] characterBounds) { + mCharacterBounds = Objects.requireNonNull(characterBounds); + return this; + } + + /** + * Set the flags of the characters. The flags of the i-th character in the editor is stored + * at index (i - start). The length of the given array must equal to (end - start). + * The flags contain the following information: + * <ul> + * <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a + * whitespace. </li> + * <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a + * linefeed. </li> + * <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a + * punctuation. </li> + * <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to + * is RTL. All all character in the same line must have the same line direction. Check + * {@link #getLineSegmentFinder()} for more information of line boundaries. </li> + * </ul> + * + * @param characterFlags the array of the character's flags. + * @throws NullPointerException if the given {@code characterFlags} is {@code null}. + * @throws IllegalArgumentException if the given {@code characterFlags} contains invalid + * flags. + * + * @see #getCharacterFlags(int) + */ + @NonNull + public Builder setCharacterFlags(@NonNull int[] characterFlags) { + Objects.requireNonNull(characterFlags); + for (int characterFlag : characterFlags) { + if ((characterFlag & (~KNOWN_CHARACTER_FLAGS)) != 0) { + throw new IllegalArgumentException("characterFlags contains invalid flags."); + } + } + mCharacterFlags = characterFlags; + return this; + } + + /** + * Set the BiDi levels for the character. The bidiLevel of the i-th character in the editor + * is stored at index (i - start). The length of the given array must equal to + * (end - start). <br/> + * + * BiDi level is defined by + * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode + * bidirectional algorithm </a>. One can determine whether a character's direction is + * right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level. + * If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a + * character must be in the range of [0, 125]. + * @param characterBidiLevels the array of the character's BiDi level. + * + * @throws NullPointerException if the given {@code characterBidiLevels} is {@code null}. + * @throws IllegalArgumentException if the given {@code characterBidiLevels} contains an + * element that's out of the range [0, 125]. + * + * @see #getCharacterBidiLevel(int) + */ + @NonNull + public Builder setCharacterBidiLevel(@NonNull int[] characterBidiLevels) { + Objects.requireNonNull(characterBidiLevels); + for (int index = 0; index < characterBidiLevels.length; ++index) { + Preconditions.checkArgumentInRange(characterBidiLevels[index], 0, 125, + "bidiLevels[" + index + "]"); + } + mCharacterBidiLevels = characterBidiLevels; + return this; + } + + /** + * Set the {@link SegmentFinder} that locates the grapheme cluster boundaries. Grapheme is + * defined in <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries"> + * the unicode annex #29: unicode text segmentation<a/>. It's a user-perspective character. + * And it's usually the minimal unit for selection, backspace, deletion etc. <br/> + * + * Please note that only the grapheme segments within the range from start to end will + * be available to the IME. The remaining information will be discarded during serialization + * for better performance. + * + * @param graphemeSegmentFinder the {@link SegmentFinder} that locates the grapheme cluster + * boundaries. + * @throws NullPointerException if the given {@code graphemeSegmentFinder} is {@code null}. + * + * @see #getGraphemeSegmentFinder() + * @see SegmentFinder + * @see SegmentFinder.DefaultSegmentFinder + */ + @NonNull + public Builder setGraphemeSegmentFinder(@NonNull SegmentFinder graphemeSegmentFinder) { + mGraphemeSegmentFinder = Objects.requireNonNull(graphemeSegmentFinder); + return this; + } + + /** + * Set the {@link SegmentFinder} that locates the word boundaries. <br/> + * + * Please note that only the word segments within the range from start to end will + * be available to the IME. The remaining information will be discarded during serialization + * for better performance. + * @param wordSegmentFinder set the {@link SegmentFinder} that locates the word boundaries. + * @throws NullPointerException if the given {@code wordSegmentFinder} is {@code null}. + * + * @see #getWordSegmentFinder() + * @see SegmentFinder + * @see SegmentFinder.DefaultSegmentFinder + */ + @NonNull + public Builder setWordSegmentFinder(@NonNull SegmentFinder wordSegmentFinder) { + mWordSegmentFinder = Objects.requireNonNull(wordSegmentFinder); + return this; + } + + /** + * Set the {@link SegmentFinder} that locates the line boundaries. Aside from the hard + * breaks in the text, it should also locate the soft line breaks added by the editor. + * It is expected that the characters within the same line is rendered on the same baseline. + * (Except for some text formatted as subscript and superscript.) <br/> + * + * Please note that only the line segments within the range from start to end will + * be available to the IME. The remaining information will be discarded during serialization + * for better performance. + * @param lineSegmentFinder set the {@link SegmentFinder} that locates the line boundaries. + * @throws NullPointerException if the given {@code lineSegmentFinder} is {@code null}. + * + * @see #getLineSegmentFinder() + * @see SegmentFinder + * @see SegmentFinder.DefaultSegmentFinder + */ + @NonNull + public Builder setLineSegmentFinder(@NonNull SegmentFinder lineSegmentFinder) { + mLineSegmentFinder = Objects.requireNonNull(lineSegmentFinder); + return this; + } + + /** + * Create the {@link TextBoundsInfo} using the parameters in this {@link Builder}. + * + * @throws IllegalStateException in the following conditions: + * <ul> + * <li>if the {@code start} or {@code end} is not set.</li> + * <li>if the {@code matrix} is not set.</li> + * <li>if {@code characterBounds} is not set or its length doesn't equal to + * 4 * ({@code end} - {@code start}).</li> + * <li>if the {@code characterFlags} is not set or its length doesn't equal to + * ({@code end} - {@code start}).</li> + * <li>if {@code graphemeSegmentFinder}, {@code wordSegmentFinder} or + * {@code lineSegmentFinder} is not set.</li> + * <li>if characters in the same line has inconsistent {@link #FLAG_LINE_IS_RTL} + * flag.</li> + * </ul> + */ + @NonNull + public TextBoundsInfo build() { + if (mStart < 0 || mEnd < 0) { + throw new IllegalStateException("Start and end must be set."); + } + + if (!mMatrixInitialized) { + throw new IllegalStateException("Matrix must be set."); + } + + if (mCharacterBounds == null) { + throw new IllegalStateException("CharacterBounds must be set."); + } + + if (mCharacterFlags == null) { + throw new IllegalStateException("CharacterFlags must be set."); + } + + if (mCharacterBidiLevels == null) { + throw new IllegalStateException("CharacterBidiLevel must be set."); + } + + if (mCharacterBounds.length != 4 * (mEnd - mStart)) { + throw new IllegalStateException("The length of characterBounds doesn't match the " + + "length of the given start and end." + + " Expected length: " + (4 * (mEnd - mStart)) + + " characterBounds length: " + mCharacterBounds.length); + } + if (mCharacterFlags.length != mEnd - mStart) { + throw new IllegalStateException("The length of characterFlags doesn't match the " + + "length of the given start and end." + + " Expected length: " + (mEnd - mStart) + + " characterFlags length: " + mCharacterFlags.length); + } + if (mCharacterBidiLevels.length != mEnd - mStart) { + throw new IllegalStateException("The length of characterBidiLevels doesn't match" + + " the length of the given start and end." + + " Expected length: " + (mEnd - mStart) + + " characterFlags length: " + mCharacterBidiLevels.length); + } + if (mGraphemeSegmentFinder == null) { + throw new IllegalStateException("GraphemeSegmentFinder must be set."); + } + if (mWordSegmentFinder == null) { + throw new IllegalStateException("WordSegmentFinder must be set."); + } + if (mLineSegmentFinder == null) { + throw new IllegalStateException("LineSegmentFinder must be set."); + } + + if (!isLineDirectionFlagConsistent(mCharacterFlags, mLineSegmentFinder, mStart, mEnd)) { + throw new IllegalStateException("characters in the same line must have the same " + + "FLAG_LINE_IS_RTL flag value."); + } + return new TextBoundsInfo(this); + } + } + + /** + * Encode the segment start and end positions in {@link SegmentFinder} to a flags array. + * + * For example: + * Text: "A BC DE" + * Input: + * start: 2, end: 7 // substring "BC DE" + * SegmentFinder: segment ranges = [(2, 4), (5, 7)] // a word break iterator + * flags: [0x0000, 0x0000, 0x0080, 0x0000, 0x0000, 0x0000] // 0x0080 is whitespace + * segmentStartFlag: 0x0100 + * segmentEndFlag: 0x0200 + * Output: + * flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200] + * The index 2 and 5 encode segment starts, the index 4 and 7 encode a segment end. + * + * @param flags the flags array to receive the results. + * @param segmentStartFlag the flag used to encode the segment start. + * @param segmentEndFlag the flag used to encode the segment end. + * @param start the start index of the encoded range, inclusive. + * @param end the end index of the encoded range, inclusive. + * @param segmentFinder the SegmentFinder to be encoded. + * + * @see #decodeSegmentFinder(int[], int, int, int, int) + */ + private static void encodeSegmentFinder(@NonNull int[] flags, int segmentStartFlag, + int segmentEndFlag, int start, int end, @NonNull SegmentFinder segmentFinder) { + if (end - start + 1 != flags.length) { + throw new IllegalStateException("The given flags array must have the same length as" + + " the given range. flags length: " + flags.length + + " range: [" + start + ", " + end + "]"); + } + + int segmentEnd = segmentFinder.nextEndBoundary(start); + if (segmentEnd == SegmentFinder.DONE) return; + int segmentStart = segmentFinder.previousStartBoundary(segmentEnd); + + while (segmentEnd != SegmentFinder.DONE && segmentEnd <= end) { + if (segmentStart >= start) { + flags[segmentStart - start] |= segmentStartFlag; + flags[segmentEnd - start] |= segmentEndFlag; + } + segmentStart = segmentFinder.nextStartBoundary(segmentStart); + segmentEnd = segmentFinder.nextEndBoundary(segmentEnd); + } + } + + /** + * Decode a {@link SegmentFinder} from a flags array. + * + * For example: + * Text: "A BC DE" + * Input: + * start: 2, end: 7 // substring "BC DE" + * flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200] + * segmentStartFlag: 0x0100 + * segmentEndFlag: 0x0200 + * Output: + * SegmentFinder: segment ranges = [(2, 4), (5, 7)] + * + * @param flags the flags array to decode the SegmentFinder. + * @param segmentStartFlag the flag to decode a segment start. + * @param segmentEndFlag the flag to decode a segment end. + * @param start the start index of the interested range, inclusive. + * @param end the end index of the interested range, inclusive. + * + * @see #encodeSegmentFinder(int[], int, int, int, int, SegmentFinder) + */ + private static SegmentFinder decodeSegmentFinder(int[] flags, int segmentStartFlag, + int segmentEndFlag, int start, int end) { + if (end - start + 1 != flags.length) { + throw new IllegalStateException("The given flags array must have the same length as" + + " the given range. flags length: " + flags.length + + " range: [" + start + ", " + end + "]"); + } + int[] breaks = ArrayUtils.newUnpaddedIntArray(10); + int count = 0; + for (int offset = 0; offset < flags.length; ++offset) { + if ((flags[offset] & segmentStartFlag) == segmentStartFlag) { + breaks = GrowingArrayUtils.append(breaks, count++, start + offset); + } + if ((flags[offset] & segmentEndFlag) == segmentEndFlag) { + breaks = GrowingArrayUtils.append(breaks, count++, start + offset); + } + } + return new SegmentFinder.DefaultSegmentFinder(Arrays.copyOf(breaks, count)); + } + + /** + * Check whether the {@link #FLAG_LINE_IS_RTL} is the same for characters in the same line. + * @return true if all characters in the same line has the same {@link #FLAG_LINE_IS_RTL} flag. + */ + private static boolean isLineDirectionFlagConsistent(int[] characterFlags, + SegmentFinder lineSegmentFinder, int start, int end) { + int segmentEnd = lineSegmentFinder.nextEndBoundary(start); + if (segmentEnd == SegmentFinder.DONE) return true; + int segmentStart = lineSegmentFinder.previousStartBoundary(segmentEnd); + + while (segmentStart != SegmentFinder.DONE && segmentStart < end) { + final int lineStart = Math.max(segmentStart, start); + final int lineEnd = Math.min(segmentEnd, end); + final boolean lineIsRtl = (characterFlags[lineStart - start] & FLAG_LINE_IS_RTL) != 0; + for (int index = lineStart + 1; index < lineEnd; ++index) { + final int flags = characterFlags[index - start]; + final boolean characterLineIsRtl = (flags & FLAG_LINE_IS_RTL) != 0; + if (characterLineIsRtl != lineIsRtl) { + return false; + } + } + + segmentStart = lineSegmentFinder.nextStartBoundary(segmentStart); + segmentEnd = lineSegmentFinder.nextEndBoundary(segmentEnd); + } + return true; + } +} diff --git a/core/java/android/view/inputmethod/TextBoundsInfoResult.java b/core/java/android/view/inputmethod/TextBoundsInfoResult.java new file mode 100644 index 000000000000..62df17a3aeae --- /dev/null +++ b/core/java/android/view/inputmethod/TextBoundsInfoResult.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.RectF; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * The object that holds the result of the + * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. + * + * @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer) + */ +public final class TextBoundsInfoResult { + private final int mResultCode; + private final TextBoundsInfo mTextBoundsInfo; + + /** + * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the + * editor doesn't implement the method. + */ + public static final int CODE_UNSUPPORTED = 0; + + /** + * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the + * editor successfully returns a {@link TextBoundsInfo}. + */ + public static final int CODE_SUCCESS = 1; + + /** + * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the + * request failed. This result code is returned when the editor can't provide a valid + * {@link TextBoundsInfo}. (e.g. The editor view is not laid out.) + */ + public static final int CODE_FAILED = 2; + + /** + * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the + * request is cancelled. This happens when the {@link InputConnection} is or becomes + * invalidated while requesting the + * {@link TextBoundsInfo}, for example because a new {@code InputConnection} was started, or + * due to {@link InputMethodManager#invalidateInput}. + */ + public static final int CODE_CANCELLED = 3; + + /** @hide */ + @IntDef(prefix = { "CODE_" }, value = { + CODE_UNSUPPORTED, + CODE_SUCCESS, + CODE_FAILED, + CODE_CANCELLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode {} + + /** + * Create a {@link TextBoundsInfoResult} object with no {@link TextBoundsInfo}. + * The given {@code resultCode} can't be {@link #CODE_SUCCESS}. + * @param resultCode the result code of the + * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. + */ + public TextBoundsInfoResult(@ResultCode int resultCode) { + this(resultCode, null); + } + + /** + * Create a {@link TextBoundsInfoResult} object. + * + * @param resultCode the result code of the + * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. + * @param textBoundsInfo the returned {@link TextBoundsInfo} of the + * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. It can't be + * null if the {@code resultCode} is {@link #CODE_SUCCESS}. + * + * @throws IllegalStateException if the resultCode is + * {@link #CODE_SUCCESS} but the given {@code textBoundsInfo} + * is null. + */ + public TextBoundsInfoResult(@ResultCode int resultCode, + @NonNull TextBoundsInfo textBoundsInfo) { + if (resultCode == CODE_SUCCESS && textBoundsInfo == null) { + throw new IllegalStateException("TextBoundsInfo must be provided when the resultCode " + + "is CODE_SUCCESS."); + } + mResultCode = resultCode; + mTextBoundsInfo = textBoundsInfo; + } + + /** + * Return the result code of the + * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. + * Its value is one of the {@link #CODE_UNSUPPORTED}, {@link #CODE_SUCCESS}, + * {@link #CODE_FAILED} and {@link #CODE_CANCELLED}. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Return the {@link TextBoundsInfo} provided by the editor. It is non-null if the + * {@code resultCode} is {@link #CODE_SUCCESS}. + * Otherwise, it can be null in the following conditions: + * <ul> + * <li>the editor doesn't support + * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)}.</li> + * <li>the editor doesn't have the text bounds information at the moment. (e.g. the editor + * view is not laid out yet.) </li> + * <li> the {@link InputConnection} is or become inactive during the request. </li> + * <ul/> + */ + @Nullable + public TextBoundsInfo getTextBoundsInfo() { + return mTextBoundsInfo; + } +} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 8f590f89fa64..56524a2c01ef 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -125,6 +125,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.TextAppearanceInfo; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationManager; import android.widget.AdapterView.OnItemClickListener; @@ -4630,14 +4631,17 @@ public class Editor { (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; boolean includeVisibleLineBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; + boolean includeTextAppearance = + (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; boolean includeAll = (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker - && !includeVisibleLineBounds); + && !includeVisibleLineBounds && !includeTextAppearance); includeEditorBounds |= includeAll; includeCharacterBounds |= includeAll; includeInsertionMarker |= includeAll; includeVisibleLineBounds |= includeAll; + includeTextAppearance |= includeAll; final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder; builder.reset(); @@ -4757,6 +4761,10 @@ public class Editor { } } + if (includeTextAppearance) { + TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView); + builder.setTextAppearanceInfo(textAppearanceInfo); + } imm.updateCursorAnchorInfo(mTextView, builder.build()); // Drop the immediate flag if any. diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b5c58fb4bfc0..1144b598e47a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -69,6 +69,7 @@ import android.graphics.BaseCanvas; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Insets; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.Path; @@ -200,6 +201,7 @@ import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.view.inputmethod.SelectRangeGesture; +import android.view.inputmethod.TextBoundsInfo; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; import android.view.inspector.InspectableProperty.FlagEntry; @@ -12953,18 +12955,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getLocalVisibleRect(rect); final RectF visibleRect = new RectF(rect); - final float[] characterBounds = new float[4 * (endIndex - startIndex)]; - mLayout.fillCharacterBounds(startIndex, endIndex, characterBounds, 0); + + final float[] characterBounds = getCharacterBounds(startIndex, endIndex, + viewportToContentHorizontalOffset, viewportToContentVerticalOffset); final int limit = endIndex - startIndex; for (int offset = 0; offset < limit; ++offset) { - final float left = - characterBounds[offset * 4] + viewportToContentHorizontalOffset; - final float top = - characterBounds[offset * 4 + 1] + viewportToContentVerticalOffset; - final float right = - characterBounds[offset * 4 + 2] + viewportToContentHorizontalOffset; - final float bottom = - characterBounds[offset * 4 + 3] + viewportToContentVerticalOffset; + final float left = characterBounds[offset * 4]; + final float top = characterBounds[offset * 4 + 1]; + final float right = characterBounds[offset * 4 + 2]; + final float bottom = characterBounds[offset * 4 + 3]; final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom); final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom); @@ -12985,6 +12984,149 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Return the bounds of the characters in the given range, in TextView's coordinates. + * + * @param start the start index of the interested text range, inclusive. + * @param end the end index of the interested text range, exclusive. + * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates. + * @param layoutTop the top of the given {@code layout} in the editor view's coordinates. + * @return the character bounds stored in a flattened array, in the editor view's coordinates. + */ + private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) { + final float[] characterBounds = new float[4 * (end - start)]; + mLayout.fillCharacterBounds(start, end, characterBounds, 0); + for (int offset = 0; offset < end - start; ++offset) { + characterBounds[4 * offset] += layoutLeft; + characterBounds[4 * offset + 1] += layoutTop; + characterBounds[4 * offset + 2] += layoutLeft; + characterBounds[4 * offset + 3] += layoutTop; + } + return characterBounds; + } + + /** + * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. + * @hide + */ + public TextBoundsInfo getTextBoundsInfo(@NonNull RectF rectF) { + final Layout layout = getLayout(); + if (layout == null) { + // No valid text layout, return null. + return null; + } + final CharSequence text = layout.getText(); + if (text == null) { + // It's impossible that a layout has no text. Check here to avoid NPE. + return null; + } + + final Matrix localToGlobalMatrix = new Matrix(); + transformMatrixToGlobal(localToGlobalMatrix); + final Matrix globalToLocalMatrix = new Matrix(); + if (!localToGlobalMatrix.invert(globalToLocalMatrix)) { + // Can't map global rectF to local coordinates, this is almost impossible in practice. + return null; + } + + final float layoutLeft = viewportToContentHorizontalOffset(); + final float layoutTop = viewportToContentVerticalOffset(); + + final RectF localRectF = new RectF(rectF); + globalToLocalMatrix.mapRect(localRectF); + localRectF.offset(-layoutLeft, -layoutTop); + + // Text length is 0. There is no character bounds, return empty TextBoundsInfo. + // rectF doesn't intersect with the layout, return empty TextBoundsInfo. + if (!localRectF.intersects(0f, 0f, layout.getWidth(), layout.getHeight()) + || text.length() == 0) { + final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(); + final SegmentFinder emptySegmentFinder = + new SegmentFinder.DefaultSegmentFinder(new int[0]); + builder.setStartAndEnd(0, 0) + .setMatrix(localToGlobalMatrix) + .setCharacterBounds(new float[0]) + .setCharacterBidiLevel(new int[0]) + .setCharacterFlags(new int[0]) + .setGraphemeSegmentFinder(emptySegmentFinder) + .setLineSegmentFinder(emptySegmentFinder) + .setWordSegmentFinder(emptySegmentFinder); + return builder.build(); + } + + final int startLine = layout.getLineForVertical((int) Math.floor(localRectF.top)); + final int endLine = layout.getLineForVertical((int) Math.floor(localRectF.bottom)); + final int start = layout.getLineStart(startLine); + final int end = layout.getLineEnd(endLine); + + // Compute character bounds. + final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop); + + // Compute character flags and BiDi levels. + final int[] characterFlags = new int[end - start]; + final int[] characterBidiLevels = new int[end - start]; + for (int line = startLine; line <= endLine; ++line) { + final int lineStart = layout.getLineStart(line); + final int lineEnd = layout.getLineEnd(line); + final Layout.Directions directions = layout.getLineDirections(line); + for (int i = 0; i < directions.getRunCount(); ++i) { + final int runStart = directions.getRunStart(i) + lineStart; + final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd); + final int runLevel = directions.getRunLevel(i); + Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel); + } + + final boolean lineIsRtl = + layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT; + for (int index = lineStart; index < lineEnd; ++index) { + int flags = 0; + if (TextUtils.isWhitespace(text.charAt(index))) { + flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE; + } + if (TextUtils.isPunctuation(Character.codePointAt(text, index))) { + flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION; + } + if (TextUtils.isNewline(Character.codePointAt(text, index))) { + flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED; + } + if (lineIsRtl) { + flags |= TextBoundsInfo.FLAG_LINE_IS_RTL; + } + characterFlags[index - start] = flags; + } + } + + // Create grapheme SegmentFinder. + final SegmentFinder graphemeSegmentFinder = + new GraphemeClusterSegmentFinder(text, layout.getPaint()); + + // Create word SegmentFinder. + final WordIterator wordIterator = getWordIterator(); + wordIterator.setCharSequence(text, 0, text.length()); + final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator); + + // Create line SegmentFinder. + final int lineCount = endLine - startLine + 1; + final int[] lineRanges = new int[2 * lineCount]; + for (int line = startLine; line <= endLine; ++line) { + final int offset = line - startLine; + lineRanges[2 * offset] = layout.getLineStart(line); + lineRanges[2 * offset + 1] = layout.getLineEnd(line); + } + final SegmentFinder lineSegmentFinder = new SegmentFinder.DefaultSegmentFinder(lineRanges); + + final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(); + builder.setStartAndEnd(start, end) + .setMatrix(localToGlobalMatrix) + .setCharacterBounds(characterBounds) + .setCharacterBidiLevel(characterBidiLevels) + .setCharacterFlags(characterFlags) + .setGraphemeSegmentFinder(graphemeSegmentFinder) + .setLineSegmentFinder(lineSegmentFinder) + .setWordSegmentFinder(wordSegmentFinder); + return builder.build(); + } + + /** * @hide */ public boolean isPositionVisible(final float positionX, final float positionY) { diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 8815ab35b671..0956a71bd92d 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -141,6 +141,10 @@ public final class TransitionInfo implements Parcelable { /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ public static final int FLAG_FIRST_CUSTOM = 1 << 17; + /** The change belongs to a window that won't contain activities. */ + public static final int FLAGS_IS_NON_APP_WINDOW = + FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -579,11 +583,16 @@ public final class TransitionInfo implements Parcelable { return mFlags; } - /** Whether the given change flags has included in this change. */ + /** Whether this change contains any of the given change flags. */ public boolean hasFlags(@ChangeFlags int flags) { return (mFlags & flags) != 0; } + /** Whether this change contains all of the given change flags. */ + public boolean hasAllFlags(@ChangeFlags int flags) { + return (mFlags & flags) == flags; + } + /** * @return the bounds of the container before the change. It may be empty if the container * is coming into existence. diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index f600c36cd8c9..330ff8e61609 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -25,6 +25,7 @@ import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.RectF; import android.os.Bundle; import android.text.Editable; import android.text.Selection; @@ -46,9 +47,12 @@ import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.view.inputmethod.SelectRangeGesture; +import android.view.inputmethod.TextBoundsInfo; +import android.view.inputmethod.TextBoundsInfoResult; import android.widget.TextView; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -233,7 +237,9 @@ public final class EditableInputConnection extends BaseInputConnection | InputConnection.CURSOR_UPDATE_MONITOR; final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER - | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS; + | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS + | InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS + | InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE; // It is possible that any other bit is used as a valid flag in a future release. // We should reject the entire request in such a case. @@ -262,6 +268,23 @@ public final class EditableInputConnection extends BaseInputConnection } @Override + public void requestTextBoundsInfo( + @NonNull RectF rectF, @Nullable @CallbackExecutor Executor executor, + @NonNull Consumer<TextBoundsInfoResult> consumer) { + final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(rectF); + final int resultCode; + if (textBoundsInfo != null) { + resultCode = TextBoundsInfoResult.CODE_SUCCESS; + } else { + resultCode = TextBoundsInfoResult.CODE_FAILED; + } + final TextBoundsInfoResult textBoundsInfoResult = + new TextBoundsInfoResult(resultCode, textBoundsInfo); + + executor.execute(() -> consumer.accept(textBoundsInfoResult)); + } + + @Override public boolean setImeConsumesInput(boolean imeConsumesInput) { if (mTextView == null) { return super.setImeConsumesInput(imeConsumesInput); diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl index ea5c9a33b762..81e060d8c648 100644 --- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl +++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl @@ -16,20 +16,15 @@ package com.android.internal.inputmethod; +import android.graphics.RectF; import android.os.Bundle; import android.os.ResultReceiver; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; -import android.view.inputmethod.DeleteGesture; -import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputContentInfo; -import android.view.inputmethod.InsertGesture; -import android.view.inputmethod.JoinOrSplitGesture; -import android.view.inputmethod.RemoveSpaceGesture; -import android.view.inputmethod.SelectGesture; -import android.view.inputmethod.SelectRangeGesture; +import android.view.inputmethod.ParcelableHandwritingGesture; import android.view.inputmethod.TextAttribute; import com.android.internal.infra.AndroidFuture; @@ -94,26 +89,8 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; void performPrivateCommand(in InputConnectionCommandHeader header, String action, in Bundle data); - void performHandwritingSelectGesture(in InputConnectionCommandHeader header, - in SelectGesture gesture, in ResultReceiver resultReceiver); - - void performHandwritingSelectRangeGesture(in InputConnectionCommandHeader header, - in SelectRangeGesture gesture, in ResultReceiver resultReceiver); - - void performHandwritingInsertGesture(in InputConnectionCommandHeader header, - in InsertGesture gesture, in ResultReceiver resultReceiver); - - void performHandwritingDeleteGesture(in InputConnectionCommandHeader header, - in DeleteGesture gesture, in ResultReceiver resultReceiver); - - void performHandwritingDeleteRangeGesture(in InputConnectionCommandHeader header, - in DeleteRangeGesture gesture, in ResultReceiver resultReceiver); - - void performHandwritingRemoveSpaceGesture(in InputConnectionCommandHeader header, - in RemoveSpaceGesture gesture, in ResultReceiver resultReceiver); - - void performHandwritingJoinOrSplitGesture(in InputConnectionCommandHeader header, - in JoinOrSplitGesture gesture, in ResultReceiver resultReceiver); + void performHandwritingGesture(in InputConnectionCommandHeader header, + in ParcelableHandwritingGesture gesture, in ResultReceiver resultReceiver); void setComposingRegion(in InputConnectionCommandHeader header, int start, int end); @@ -130,6 +107,9 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, in AndroidFuture future /* T=Boolean */); + void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF rect, + in ResultReceiver resultReceiver /* T=TextBoundsInfoResult */); + void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo, int flags, in Bundle opts, in AndroidFuture future /* T=Boolean */); diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java index 98d81c9598b8..6870d09c8a7f 100644 --- a/core/java/com/android/internal/os/RoSystemProperties.java +++ b/core/java/com/android/internal/os/RoSystemProperties.java @@ -51,6 +51,15 @@ public class RoSystemProperties { // ------ ro.fw.* ------------ // public static final boolean FW_SYSTEM_USER_SPLIT = SystemProperties.getBoolean("ro.fw.system_user_split", false); + /** + * Indicates whether the device should run in headless system user mode, + * in which user 0 only runs the system, not a real user. + * <p>WARNING about changing this value during an non-wiping update (OTA): + * <li>If this value is modified via an update, the change will have no effect, since an + * already-existing system user cannot change its mode. + * <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to + * do so will corrupt the system user. + */ public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER = SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 1d4b246de5c8..5b5b15fb40eb 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -23,7 +23,7 @@ import android.graphics.Rect; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.os.Bundle; @@ -175,9 +175,9 @@ oneway interface IStatusBar void setBiometicContextListener(in IBiometricContextListener listener); /** - * Sets an instance of IUdfpsHbmListener for UdfpsController. + * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController. */ - void setUdfpsHbmListener(in IUdfpsHbmListener listener); + void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback); /** * Notifies System UI that the display is ready to show system decorations. diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index ef8f2db5ff57..d19068130b6d 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -23,7 +23,7 @@ import android.graphics.Rect; import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.net.Uri; @@ -136,9 +136,9 @@ interface IStatusBarService void setBiometicContextListener(in IBiometricContextListener listener); /** - * Sets an instance of IUdfpsHbmListener for UdfpsController. + * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController. */ - void setUdfpsHbmListener(in IUdfpsHbmListener listener); + void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback); /** * Show a warning that the device is about to go to sleep due to user inactivity. diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp index 2cd9f8966bb2..6762e45d0b79 100644 --- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp +++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp @@ -22,6 +22,7 @@ #include <errno.h> #include <fcntl.h> #include <inttypes.h> +#include <linux/fs.h> #include <nativehelper/ScopedUtfChars.h> #include <stdlib.h> #include <string.h> @@ -250,6 +251,16 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr return INSTALL_FAILED_CONTAINER_ERROR; } + // If a filesystem like f2fs supports per-file compression, set the compression bit before data + // writes + unsigned int flags; + if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) { + ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno)); + } else if ((flags & FS_COMPR_FL) == 0) { + flags |= FS_COMPR_FL; + ioctl(fd, FS_IOC_SETFLAGS, &flags); + } + if (!zipFile->uncompressEntry(zipEntry, fd)) { ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName); close(fd); diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 9e4f63cab86c..465000069aab 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -978,6 +978,11 @@ message UserControllerProto { } repeated UserProfile user_profile_group_ids = 4; repeated int32 visible_users_array = 5; + + // current_user contains the id of the current user, while current_profiles contains the ids of + // both the current user and its profiles (if any) + optional int32 current_user = 6; + repeated int32 current_profiles = 7; } // sync with com.android.server.am.AppTimeTracker.java diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2b97a345c11d..16e0a5967e78 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1150,6 +1150,18 @@ android:description="@string/permdesc_readMediaImages" android:protectionLevel="dangerous" /> + <!-- Allows an application to read image or video files from external storage that a user has + selected via the permission prompt photo picker. Apps can check this permission to verify that + a user has decided to use the photo picker, instead of granting access to + {@link #READ_MEDIA_IMAGES or #READ_MEDIA_VIDEO}. It does not prevent apps from accessing the + standard photo picker manually. + <p>Protection level: dangerous --> + <permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" + android:permissionGroup="android.permission-group.UNDEFINED" + android:label="@string/permlab_readVisualUserSelect" + android:description="@string/permdesc_readVisualUserSelect" + android:protectionLevel="dangerous" /> + <!-- Allows an application to write to external storage. <p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or higher, this permission has no effect. @@ -3152,6 +3164,12 @@ <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/> + <!-- Allows an application to hint that a broadcast is associated with an + "interactive" usage scenario + @hide --> + <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi Must be required by activities that handle the intent action {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system. diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 0ebce4019258..b20bfef07976 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID se verstek is nie beperk nie. Volgende oproep: nie beperk nie"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Diens nie verskaf nie."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Jy kan nie die beller-ID-instelling verander nie."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Geen mobiele datadiens nie"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Noodoproepe is onbeskikbaar"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Geen stemdiens nie"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index a861f3cffa6b..2574fd8fba76 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"የደዋይ ID ነባሪዎች ወደአልተከለከለም። ቀጥሎ ጥሪ፡አልተከለከለም"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"አገልግሎት አልቀረበም።"</string> <string name="CLIRPermanent" msgid="166443681876381118">"የደዋይ መታወቂያ ቅንብሮች መለወጥ አትችልም፡፡"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ምንም የተንቀሳቃሽ ስልክ ውሂብ አገልግሎት የለም"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"የድንገተኛ አደጋ ጥሪ አይገኝም"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ምንም የድምፅ ጥሪ አገልግሎት የለም"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 7623fb1682da..ba3ebb1a8884 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -76,6 +76,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"الإعداد التلقائي لمعرف المتصل هو غير محظور . الاتصال التالي: غير محظور"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"الخدمة غير متوفرة."</string> <string name="CLIRPermanent" msgid="166443681876381118">"لا يمكنك تغيير إعداد معرّف المتصل."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"لا تتوفّر خدمة بيانات جوّال."</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"لا تتوفّر مكالمات طوارئ."</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"لا تتوفر خدمة صوتية"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 3a97bafe26cf..d49d3b1a6417 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"সুবিধা যোগান ধৰা হোৱা নাই।"</string> <string name="CLIRPermanent" msgid="166443681876381118">"আপুনি কলাৰ আইডি ছেটিং সলনি কৰিব নোৱাৰে।"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"কোনো ম’বাইল ডেটা সেৱা নাই"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"জৰুৰীকালীন কল কৰাৰ সুবিধা উপলব্ধ নহয়"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"কোনো ভইচ সেৱা নাই"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 0b361aca0d96..ffd1480da8ca 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Xidmət təmin edilməyib."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Çağrı kimliyi ayarını dəyişə bilməzsiniz."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil data xidməti yoxdur"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Təcili zəng əlçatan deyil"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Səsli xidmət yoxdur"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 078c098efd58..b1ec940bb865 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: Nije ograničen."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije dobavljena."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete da promenite podešavanje ID-a korisnika."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge mobilnih podataka"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi nisu dostupni"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema glasovne usluge"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 023e82c774ec..40605c5b4389 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Налады ідэнтыфікатару АВН па змаўчанні: не абмяжавана. Наступны выклік: не абмежавана"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Служба не прадастаўляецца."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Вы не можаце змяніць налады ідэнтыфікатара абанента, якi тэлефануе."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мабільная перадача даных недаступная"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Экстранныя выклікі недаступныя"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Няма сэрвісу галасавых выклікаў"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index aaa080a355ba..a1c9d1c7a628 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е разрешена."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е обезпечена."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да променяте настройката за идентификация на обажданията."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Няма достъп до мобилната услуга за данни"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Няма достъп до спешните обаждания"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Няма услуга за гласови обаждания"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index ee1db8e89ebd..fd69acdb859a 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"পরিষেবা প্রস্তুত নয়৷"</string> <string name="CLIRPermanent" msgid="166443681876381118">"আপনি কলার আইডি এর সেটিংস পরিবর্তন করতে পারবেন না৷"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"মোবাইল ডেটা পরিষেবা নেই"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"জরুরি কল করা যাবে না"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ভয়েস পরিষেবা নেই"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 20f6bc12a952..455c9e95c9df 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -73,6 +73,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: nije zabranjen"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Uslugu nije moguće koristiti."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavke ID-a pozivaoca."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"To uvijek možete promijeniti u postavkama"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge prijenosa podataka na mobilnoj mreži"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi su nedostupni"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema usluge govornih poziva"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 2142b6093dbb..43dc6764e8bd 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: no restringit"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"No s\'ha proveït el servei."</string> <string name="CLIRPermanent" msgid="166443681876381118">"No pots canviar la configuració de l\'identificador de trucada."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hi ha servei de dades mòbils"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Les trucades d\'emergència no estan disponibles"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sense servei de veu"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 7720d083409c..132a9f5bdc89 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Neomezeno"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba není zřízena."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Nastavení ID volajícího nesmíte měnit."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Není k dispozici žádná mobilní datová služba"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Tísňová volání jsou nedostupná"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hlasová volání nejsou k dispozici"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index ecd64070a75b..9dd43341df34 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Standarder for opkalds-id til ikke begrænset. Næste opkald: Ikke begrænset"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjenesten provisioneres ikke."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke ændre indstillingen for opkalds-id\'et."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Der blev skiftet til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-data"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"Du kan altid ændre dette under Indstillinger"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjeneste"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Det er ikke muligt at foretage nødopkald"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ingen taletjeneste"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 3d5985c9f1cc..661c2cda1289 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Nicht beschränkt"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Dienst nicht eingerichtet."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Du kannst die Einstellung für die Anrufer-ID nicht ändern."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Kein mobiler Datendienst"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Notrufe nicht möglich"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Keine Anrufe"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index f84a9fb01066..8cb3989ba55a 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"μη περιορισμένη\". Επόμενη κλήση: Μη περιορισμένη"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Η υπηρεσία δεν προβλέπεται."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Δεν μπορείτε να αλλάξετε τη ρύθμιση του αναγνωριστικού καλούντος."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Έγινε εναλλαγή των δεδομένων σε <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"Μπορείτε να αλλάξετε αυτήν την επιλογή ανά πάσα στιγμή στις Ρυθμίσεις"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Δεν υπάρχει υπηρεσία δεδομένων κινητής τηλεφωνίας"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Οι κλήσεις έκτακτης ανάγκης δεν είναι διαθέσιμες"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Δεν υπάρχει φωνητική υπηρεσία"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 4c0510bef4cf..75db3826ac48 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string> <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 875ddf9f3829..883cd55ddaa3 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string> <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 6e034b730083..c4c19da5d2b2 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string> <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 643f27f49220..d19199d5ae08 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string> <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 91e99fff4c7a..9037d70ec3b4 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string> <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this anytime in Settings"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 6a45205a3558..9441f27d5daa 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El Identificador de llamadas está predeterminado en no restringido. Llamada siguiente: no restringido"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servicio no suministrado."</string> <string name="CLIRPermanent" msgid="166443681876381118">"No puedes cambiar la configuración del identificador de llamadas."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hay ningún servicio de datos móviles"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Servicio de llamadas de emergencia no disponible"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sin servicio de voz"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 66f67b368c72..753aa8b93c5e 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"La identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: No restringido"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"El servicio no se suministra."</string> <string name="CLIRPermanent" msgid="166443681876381118">"No puedes modificar la identificación de emisor."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hay ningún servicio de datos móviles"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Servicio de llamadas de emergencia no disponible"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sin servicio de voz"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 349a6b25c0c5..17897935cd86 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Helistaja ID pole vaikimisi piiratud. Järgmine kõne: pole piiratud"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Teenus pole ette valmistatud."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Helistaja ID seadet ei saa muuta."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobiilne andmesideteenus puudub"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hädaabikõned pole saadaval"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Häälkõned pole saadaval"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index d4759d5bf77b..8a7d0734a620 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ez dago mugikorreko datu-zerbitzurik"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ezin da egin larrialdi-deirik"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ez dago ahots-deien zerbitzurik"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 4064353038b8..88bbcc62f2e0 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"پیشفرض شناسه تماسگیرنده روی غیرمحدود است. تماس بعدی: بدون محدودیت"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"سرویس دارای مجوز نیست."</string> <string name="CLIRPermanent" msgid="166443681876381118">"شما میتوانید تنظیم شناسه تماسگیرنده را تغییر دهید."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"بدون سرویس داده تلفن همراه"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"تماس اضطراری دردسترس نیست"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"سرویس صوتی دردسترس نیست"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 8fedfb74fd1b..da3a91ef28be 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: ei rajoitettu"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Palvelua ei tarjota."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Et voi muuttaa soittajan tunnuksen asetusta."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ei mobiilidatapalvelua"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hätäpuhelut eivät ole käytettävissä"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ei äänipuheluja"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index e63b73436df6..d05d97bcdeae 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Aucun service de données cellulaires"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Le service d\'appel d\'urgence n\'est pas accessible"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Aucun service vocal"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 019fdf2840b5..cb2e201eafb5 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Aucun service de données mobiles"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Appels d\'urgence non disponibles"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Aucun service vocal"</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 219299fed39a..a3aed4138ed8 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizo non ofrecido."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Non podes cambiar a configuración do identificador de chamada."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Non hai servizo de datos para móbiles"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"As chamadas de emerxencia non están dispoñibles"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Non hai servizo de chamadas de voz"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 90dda1aa6841..2d9210b34e7f 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"સેવાની જોગવાઈ કરી નથી."</string> <string name="CLIRPermanent" msgid="166443681876381118">"તમે કૉલર ID સેટિંગ બદલી શકતાં નથી."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"કોઈ મોબાઇલ ડેટા સેવા નથી"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"કોટોકટીની કૉલિંગ સેવા અનુપલબ્ધ"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"કોઈ વૉઇસ સેવા નથી"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index af5bc1f067f0..9f0e9367a7a1 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित नहीं"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवा प्रावधान की हुई नहीं है."</string> <string name="CLIRPermanent" msgid="166443681876381118">"आप कॉलर आईडी सेटिंग नहीं बदल सकते."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"मोबाइल डेटा सेवा पर रोक लगा दी गई है"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आपातकालीन कॉल पर रोक लगा दी गई है"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"कोई वॉइस सेवा नहीं है"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 87df29a689ee..40a49db1c1fa 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -73,6 +73,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zadana postavka ID-a pozivatelja nema ograničenje. Sljedeći poziv: Nije ograničen"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije rezervirana."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavku ID-a pozivatelja."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"To uvijek možete promijeniti u postavkama"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema podatkovne mobilne usluge"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi nisu dostupni"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema glasovnih usluga"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 3762fdeb4bcd..cf6132f2fbbd 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: nem korlátozott"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"A szolgáltatás nincs biztosítva."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Nem tudja módosítani a hívó fél azonosítója beállítást."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nincs mobiladat-szolgáltatás"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Segélyhívás nem lehetséges"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hangszolgáltatás letiltva"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index a11e24be55b5..4da9e3be3e15 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ծառայությունը չի տրամադրվում:"</string> <string name="CLIRPermanent" msgid="166443681876381118">"Դուք չեք կարող փոխել զանգողի ID-ի կարգավորումները:"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Բջջային ինտերնետի ծառայությունն արգելափակված է"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Շտապ կանչերը հասանելի չեն"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ձայնային ծառայությունն անհասանելի է"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index dbccee947097..2f2e524ebcbb 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Layanan tidak diperlengkapi."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak dapat mengubah setelan ID penelepon."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Tidak ada layanan data seluler"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Panggilan darurat tidak tersedia"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tidak ada layanan panggilan suara"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index cfefc03d59d0..82a1b66f952a 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Án takmarkana"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Þjónustu ekki útdeilt."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Þú getur ekki breytt stillingu númerabirtingar."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Engin gagnaþjónusta fyrir farsíma"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Neyðarsímtöl eru ekki í boði"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Símtöl eru ekki í boði"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index b05bf7978904..b499eea1c5e5 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID chiamante generalmente non limitato. Prossima chiamata: non limitato"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizio non fornito."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Non è possibile modificare l\'impostazione ID chiamante."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nessun servizio dati mobile"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chiamate di emergenza non disponibili"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nessun servizio di telefonia"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 8656fcee9a2f..2ef493472d65 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -74,6 +74,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"זיהוי מתקשר עובר כברירת מחדל למצב לא מוגבל. השיחה הבאה: לא מוגבלת"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"השירות לא הוקצה."</string> <string name="CLIRPermanent" msgid="166443681876381118">"אינך יכול לשנות את הגדרת זיהוי המתקשר."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"הנתונים עברו אל <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"תמיד אפשר לשנות זאת ב\'הגדרות\'"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"אין שירות של חבילת גלישה"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"שיחות חירום לא זמינות"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"אין אפשרות לבצע שיחות רגילות"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 69d0b9d2c12d..53fa0575a14a 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"既定: 発信者番号通知、次の発信: 通知"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"提供可能なサービスがありません。"</string> <string name="CLIRPermanent" msgid="166443681876381118">"発信者番号の設定は変更できません。"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"モバイルデータ サービスのブロック"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"緊急通報のブロック"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"音声通話サービス停止"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 6d32f2590034..57ff75ab3c83 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: არ არის დაფარული."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"სერვისი არ არის მიწოდებული."</string> <string name="CLIRPermanent" msgid="166443681876381118">"არ შეგიძლიათ აბონენტის ID პარამეტრების შეცვლა."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"მობილური ინტერნეტის სერვისი არ არის"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"გადაუდებელი ზარი მიუწვდომელია"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ხმოვანი ზარების სერვისი არ არის"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 0d9fd3ff215e..8f5b1c060240 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік интернет қызметі жоқ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Жедел қызметке қоңырау шалу қолжетімді емес"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дауыстық қоңыраулар қызметі жоқ"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 0c82b663a033..1eae9dbab013 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"មិនបានដាក់កម្រិតលំនាំដើមលេខសម្គាល់អ្នកហៅ។ ការហៅបន្ទាប់៖ មិនបានដាក់កម្រិត។"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"មិនបានផ្ដល់សេវាកម្ម។"</string> <string name="CLIRPermanent" msgid="166443681876381118">"អ្នកមិនអាចប្ដូរការកំណត់លេខសម្គាល់អ្នកហៅបានទេ។"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"គ្មានសេវាកម្មទិន្នន័យចល័តទេ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ការហៅបន្ទាន់មិនអាចប្រើបានទេ"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"គ្មានសេវាកម្មជាសំឡេងទេ"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index e27527f0186c..821138a18cb7 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿಲ್ಲ"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"ಸೇವೆಯನ್ನು ಪೂರೈಸಲಾಗಿಲ್ಲ."</string> <string name="CLIRPermanent" msgid="166443681876381118">"ನೀವು ಕಾಲರ್ ID ಸೆಟ್ಟಿಂಗ್ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ಮೊಬೈಲ್ ಡೇಟಾ ಸೇವೆಯಿಲ್ಲ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ತುರ್ತು ಕರೆ ಲಭ್ಯವಿಲ್ಲ"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ಧ್ವನಿ ಸೇವೆಯಿಲ್ಲ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index c953a39e8386..5f3c7cad0f62 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한되지 않음"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"서비스가 준비되지 않았습니다."</string> <string name="CLIRPermanent" msgid="166443681876381118">"발신자 번호 설정을 변경할 수 없습니다."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"모바일 데이터 서비스가 차단됨"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"긴급 전화를 사용할 수 없음"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"음성 서비스를 이용할 수 없음"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index dccc4a6a0d8e..6e7b355f1028 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары жөндөөлөрүн өзгөртө албайсыз."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобилдик Интернет кызматы жок"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Шашылыш чалуу бөгөттөлгөн"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Аудио чалуу кызматы бөгөттөлгөн"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index c6524de6915b..7a76c2c2641b 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ບໍ່ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"ບໍ່ໄດ້ເປີດໃຊ້ບໍລິການ."</string> <string name="CLIRPermanent" msgid="166443681876381118">"ທ່ານບໍ່ສາມາດປ່ຽນແປງການຕັ້ງຄ່າ Caller ID"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ບໍ່ມີບໍລິການອິນເຕີເນັດມືຖື"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ບໍ່ສາມາດໃຊ້ການໂທສຸກເສີນໄດ້"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ບໍ່ມີບໍລິການໂທສຽງ"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index adf30e8145e2..e0d1fc7564f3 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Skambintojo ID pagal numatytuosius nustatymus yra neapribotas. Kitas skambutis: neapribotas"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Paslauga neteikiama."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Negalima pakeisti skambinančiojo ID nustatymo."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Duomenų paslaugos mobiliesiems nėra"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Skambučių pagalbos numeriu paslaugos nėra"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Balso skambučių paslauga neteikiama"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 5631521d3c49..ec8cb7d69681 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: nav ierobežots"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Pakalpojums netiek nodrošināts."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Zvanītāja ID iestatījumu nevar mainīt."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nav pieejams neviens datu pakalpojums mobilajām ierīcēm"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Nav pieejami ārkārtas izsaukumi"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Balss izsaukumu pakalpojums nedarbojas"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index a45d0a7a0f97..442047ddcb95 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандардно, ID на повикувач не е скриен. Следен повик: не е скриен"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е предвидена."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Не може да го промените поставувањето за ID на повикувач."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуга за мобилен интернет"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Итните повици се недостапни"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема услуга за говорни повици"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 492cd5402fb5..5b59e66ce01f 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"നിയന്ത്രിക്കേണ്ടതല്ലാത്ത സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടില്ല"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"സേവനം വ്യവസ്ഥ ചെയ്തിട്ടില്ല."</string> <string name="CLIRPermanent" msgid="166443681876381118">"വിളിച്ച നമ്പർ ക്രമീകരണം നിങ്ങൾക്ക് മാറ്റാനാവില്ല."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> എന്നതിലേക്ക് ഡാറ്റ മാറ്റി"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"നിങ്ങൾക്ക് ക്രമീകരണത്തിൽ ഏതുസമയത്തും ഇത് മാറ്റാം"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"മൊബൈൽ ഡാറ്റാ സേവനമില്ല"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"എമർജൻസി കോളിംഗ് ലഭ്യമല്ല"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"വോയ്സ് സേവനമില്ല"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 2c8aaae25565..03deebb66ce1 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдсан"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Үйлчилгээ провишн хийгдээгүй ."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Та дуудлага хийгчийн ID тохиргоог солиж чадахгүй."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобайл дата үйлчилгээ алга"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Яаралтай дуудлага боломжтой"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дуу хоолойны үйлчилгээ алга"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index d47fea35b675..1d25e67016e5 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आयडी डीफॉल्ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवेची तरतूद केलेली नाही."</string> <string name="CLIRPermanent" msgid="166443681876381118">"तुम्ही कॉलर आयडी सेटिंग बदलू शकत नाही."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"मोबाइल डेटा सेवा नाही"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आणीबाणी कॉलिंग अनुपलब्ध आहे"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"व्हॉइस सेवा नाही"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index deb343de11b1..72d3f2f3b0bb 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pemanggil secara lalainya ditetapkan kepada tidak dihadkan. Panggilan seterusnya: Tidak terhad"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Perkhidmatan yang tidak diuntukkan."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak boleh mengubah tetapan ID pemanggil."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Tiada perkhidmatan data mudah alih"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Panggilan kecemasan tidak tersedia"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tiada perkhidmatan suara"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 5f4167284217..5ff033dd1e47 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"ဝန်ဆောင်မှုအား ကန့်သတ်မထားပါ"</string> <string name="CLIRPermanent" msgid="166443681876381118">"သင်သည် ခေါ်ဆိုသူ ID ဆက်တင်ကို မပြောင်းလဲနိုင်ပါ။"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"မိုဘိုင်း ဒေတာဝန်ဆောင်မှု မရှိပါ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"အရေးပေါ်ခေါ်ဆိုမှု မရနိုင်ပါ"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ဖုန်းဝန်ဆောင်မှု မရှိပါ"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 0c9a98396e7f..bb4fbe4a2c99 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummervisning er ikke begrenset som standard. Neste anrop: Ikke begrenset"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"SIM-kortet er ikke tilrettelagt for tjenesten."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke endre innstillingen for anrops-ID."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjeneste"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Nødanrop er utilgjengelig"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ingen taletjeneste"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index e95d48b06947..ea96df0d69f9 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवाको व्यवस्था छैन।"</string> <string name="CLIRPermanent" msgid="166443681876381118">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"कुनै पनि मोबाइल डेटा सेवा उपलब्ध छैन"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आपत्कालीन कल सेवा उपलब्ध छैन"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"कुनै पनि भ्वाइस सेवा उपलब्ध छैन"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 57235106789b..24877ddb8248 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: onbeperkt."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service niet voorzien."</string> <string name="CLIRPermanent" msgid="166443681876381118">"U kunt de instelling voor de beller-ID niet wijzigen."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Geen service voor mobiele data"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Noodoproepen niet beschikbaar"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Geen belservice"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index c4150dc43049..456fc8394401 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"ସେବାର ସୁବିଧା ନାହିଁ।"</string> <string name="CLIRPermanent" msgid="166443681876381118">"ଆପଣ କଲର୍ ID ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"କୌଣସି ମୋବାଇଲ୍ ଡାଟା ସେବା ନାହିଁ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ଜରୁରୀକାଳୀନ କଲ୍ ଉପଲବ୍ଧ ନାହିଁ"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"କୌଣସି ଭଏସ୍ ସେବା ନାହିଁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 96917c03b92c..79ab897bb561 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string> <string name="CLIRPermanent" msgid="166443681876381118">"ਤੁਸੀਂ ਕਾਲਰ ਆਈ.ਡੀ. ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ਕੋਈ ਮੋਬਾਈਲ ਡਾਟਾ ਸੇਵਾ ਨਹੀਂ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਉਪਲਬਧ ਨਹੀਂ"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ਕੋਈ ਆਵਾਜ਼ੀ ਸੇਵਾ ਨਹੀਂ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index be9d3227a1f3..5e3a64a2280a 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: nie zastrzeżony"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usługa nie jest świadczona."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Nie możesz zmienić ustawienia ID rozmówcy."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Brak komórkowej usługi transmisji danych"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Połączenia alarmowe są niedostępne"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Brak usługi połączeń głosowych"</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index ff352b13694e..34c0f0cc8a87 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nenhum serviço móvel de dados"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index d343af6487b2..0fb835bb071f 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -73,6 +73,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID do autor da chamada é predefinido com não restrito. Chamada seguinte: Não restrita"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Serviço não fornecido."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Não pode alterar a definição da identificação de chamadas."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Os dados móveis foram alterados para o operador <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"Pode alterar isto em qualquer altura nas Definições"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Sem serviço de dados móveis"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index ff352b13694e..34c0f0cc8a87 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nenhum serviço móvel de dados"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index b560b075254a..88aab00c36ee 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Fără serviciu de date mobile"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Apelurile de urgență nu sunt disponibile"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Fără servicii vocale"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index fbe67e2617e1..33bfe77889ac 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Идентификация абонента по умолчанию не запрещена. След. вызов: разрешена"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга не предоставляется."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Невозможно изменить параметр идентификатора вызывающего абонента."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильный Интернет недоступен"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Экстренные вызовы недоступны"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Голосовые вызовы недоступны"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 4cec8777d9c1..dd5b817b8bf0 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"සේවාවන් සපයා නැත."</string> <string name="CLIRPermanent" msgid="166443681876381118">"අමතන්නාගේ ID සැකසීම ඔබට වෙනස්කල නොහැක."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ජංගම දත්ත සේවාව නැත"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"හදිසි ඇමතුම් ලබා ගත නොහැකිය"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"හඬ සේවාව නැත"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index b98364a1b46a..40abf797e53c 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba nie je poskytovaná."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Nemôžete meniť nastavenie identifikácie volajúcich."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Žiadna mobilná dátová služba"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Tiesňové volania nie sú k dispozícii"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Žiadne hlasové hovory"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 6972abb06877..53a032121058 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: ni omejeno"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Storitev ni nastavljena in omogočena."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ne morete spremeniti nastavitve ID-ja klicatelja."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ni mobilne podatkovne storitve"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Klicanje v sili ni na voljo"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ni storitve za glasovne klice"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index cbac0f021f6e..6c3f145df951 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e pakufizuar!"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Shërbimi nuk është përgatitur."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Nuk mund ta ndryshosh cilësimin e ID-së së telefonuesit."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nuk ka shërbim të të dhënave celulare"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Telefonatat e urgjencës nuk ofrohen"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nuk ka shërbim zanor"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index d5549e71d9bd..d23b056e9a77 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -73,6 +73,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ИД позиваоца подразумевано није ограничен. Следећи позив: Није ограничен."</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга није добављена."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да промените подешавање ИД-а корисника."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуге мобилних података"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Хитни позиви нису доступни"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема гласовне услуге"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index d1c579d1eafb..ee6a8ac0d6d3 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Inte blockerad"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjänsten är inte etablerad."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Det går inte att ändra inställningen för nummerpresentatör."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjänst"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Det går inte att ringa nödsamtal"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tjänsten för röstsamtal har blockerats"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 2bc3f5765c05..7b9d89f603ab 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo: Haijazuiliwa"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Huduma haitathminiwi."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Hauwezi kubadilisha mpangilio wa kitambulisho cha anayepiga."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Hakuna huduma ya data kwa vifaa vya mkononi"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Huduma ya kupiga simu za dharura haipatikani"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hakuna huduma za simu za sauti"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 9e48a47acd79..2a0fc2c7f767 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"சேவை ஒதுக்கப்படவில்லை."</string> <string name="CLIRPermanent" msgid="166443681876381118">"அழைப்பாளர் ஐடி அமைப்பை மாற்ற முடியாது."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"மொபைல் டேட்டா சேவையைப் பயன்படுத்த முடியாது"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"அவசர அழைப்பைச் செய்ய முடியாது"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"குரல் சேவை இல்லை"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index eff08bcbb898..a7c2d4e9058f 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"కాలర్ ID ఆటోమేటిక్లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి లేదు"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"సేవ కేటాయించబడలేదు."</string> <string name="CLIRPermanent" msgid="166443681876381118">"మీరు కాలర్ ID సెట్టింగ్ను మార్చలేరు."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"మొబైల్ డేటా సేవ లేదు"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"అత్యవసర కాలింగ్ అందుబాటులో లేదు"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"వాయిస్ సర్వీస్ లేదు"</string> @@ -2071,7 +2075,7 @@ <string name="notification_appops_camera_active" msgid="8177643089272352083">"కెమెరా"</string> <string name="notification_appops_microphone_active" msgid="581333393214739332">"మైక్రోఫోన్"</string> <string name="notification_appops_overlay_active" msgid="5571732753262836481">"మీ స్క్రీన్పై ఇతర యాప్ల ద్వారా ప్రదర్శించబడుతోంది"</string> - <string name="notification_feedback_indicator" msgid="663476517711323016">"ఫీడ్బ్యాక్ను అందించండి"</string> + <string name="notification_feedback_indicator" msgid="663476517711323016">"ఫీడ్బ్యాక్ ఇవ్వండి"</string> <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"ఈ నోటిఫికేషన్, ఆటోమేటిక్ సెట్టింగ్కు ప్రమోట్ చేయబడింది. ఫీడ్బ్యాక్ను అందించడానికి ట్యాప్ చేయండి."</string> <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"ఈ నోటిఫికేషన్ స్థాయి నిశ్శబ్దంగా ఉండేలా తగ్గించబడింది. ఫీడ్బ్యాక్ను అందించడానికి ట్యాప్ చేయండి."</string> <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"ఈ నోటిఫికేషన్కు ఎక్కువ ర్యాంక్ ఇవ్వబడింది. ఫీడ్బ్యాక్ను అందించడానికి ట్యాప్ చేయండి."</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 72cbc3661549..c0ab275add7f 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นไม่จำกัด การโทรครั้งต่อไป: ไม่จำกัด"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"ไม่มีการนำเสนอบริการ"</string> <string name="CLIRPermanent" msgid="166443681876381118">"คุณไม่สามารถเปลี่ยนการตั้งค่าหมายเลขผู้โทร"</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"เปลี่ยนไปใช้อินเทอร์เน็ตมือถือของ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> แล้ว"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"คุณเปลี่ยนตัวเลือกนี้ได้ทุกเมื่อในการตั้งค่า"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ไม่มีบริการเน็ตมือถือ"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"หมายเลขฉุกเฉินไม่พร้อมใช้งาน"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ไม่มีบริการเสียง"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index e66999d6cb96..7b9807af7c0e 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -72,6 +72,8 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Naka-default na hindi pinaghihigpitan ang Caller ID. Susunod na tawag: Hindi pinaghihigpitan"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Hindi naprobisyon ang serbisyo."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Hindi mo mababago ang setting ng caller ID."</string> + <string name="auto_data_switch_title" msgid="3286350716870518297">"Nailipat sa <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ang data"</string> + <string name="auto_data_switch_content" msgid="803557715007110959">"Puwede mo itong baguhin anumang oras sa Mga Setting"</string> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Walang serbisyo ng data sa mobile"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hindi available ang pang-emergency na pagtawag"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Walang serbisyo para sa boses"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index b6c4b4a61d4d..3459cb07491a 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmamış"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Hizmet sağlanamadı."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Arayanın kimliği ayarını değiştiremezsiniz."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil veri hizmeti yok"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Acil durum çağrısı kullanılamaz"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sesli çağrı hizmeti yok"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index c408c5133ebb..2483a30365b6 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -74,6 +74,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: не обмежений"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Службу не ініціалізовано."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ви не можете змінювати налаштування ідентифікатора абонента."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Службу передавання мобільних даних заблоковано"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Екстрені виклики недоступні"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Немає голосової служби"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 778443158799..7de793c214c2 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"سروس فراہم نہیں کی گئی۔"</string> <string name="CLIRPermanent" msgid="166443681876381118">"آپ کالر ID کی ترتیبات تبدیل نہیں کر سکتے ہیں۔"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"کوئی موبائل ڈیٹا سروس دستیاب نہیں ہے"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ہنگامی کالنگ دستیاب نہیں ہے"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"کوئی صوتی سروس نہیں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 5ac689d11930..8c8858512d7c 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklanmagan"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Xizmat ishalamaydi."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Qo‘ng‘iroq qiluvchining ID raqami sozlamasini o‘zgartirib bo‘lmaydi."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil internet ishlamayapti"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Favqulodda chaqiruv ishlamayapti"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ovozli chaqiruvlar ishlamaydi"</string> @@ -1592,7 +1596,7 @@ <string name="issued_by" msgid="7872459822431585684">"Tegishli:"</string> <string name="validity_period" msgid="1717724283033175968">"Yaroqliligi:"</string> <string name="issued_on" msgid="5855489688152497307">"Chiqarilgan sanasi:"</string> - <string name="expires_on" msgid="1623640879705103121">"Amal qilish muddati:"</string> + <string name="expires_on" msgid="1623640879705103121">"Muddati:"</string> <string name="serial_number" msgid="3479576915806623429">"Serial raqam:"</string> <string name="fingerprints" msgid="148690767172613723">"Barmoq izlari:"</string> <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 barmoq izi:"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index e1b479cc295e..68bb062e42c6 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Dịch vụ không được cấp phép."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Bạn không thể thay đổi cài đặt ID người gọi."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Không có dịch vụ dữ liệu di động"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Không có dịch vụ gọi khẩn cấp"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Không có dịch vụ thoại"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 5fce25e0ecdd..f2b033e5a1d9 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"默认显示本机号码,在下一次通话中也显示"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供服务。"</string> <string name="CLIRPermanent" msgid="166443681876381118">"您无法更改来电显示设置。"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"无法使用移动数据服务"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"无法使用紧急呼救服务"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"无法使用语音通话服务"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 61a3f2048fbe..63995bbc6486 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示來電號碼,下一通電話也繼續顯示。"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供此服務。"</string> <string name="CLIRPermanent" msgid="166443681876381118">"您無法更改來電顯示設定。"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"無法使用流動數據服務"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"無法撥打緊急電話"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"沒有語音服務"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 10dc699edf9b..55f9321cd9ec 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示本機號碼,下一通電話也繼續顯示。"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"無法提供此服務。"</string> <string name="CLIRPermanent" msgid="166443681876381118">"你無法變更來電顯示設定。"</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"沒有行動數據傳輸服務"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"無法撥打緊急電話"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"無法使用語音通話服務"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 66d639ebb583..2218b7687720 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -72,6 +72,10 @@ <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Aluvinjelwe"</string> <string name="serviceNotProvisioned" msgid="8289333510236766193">"Isevisi ayilungiselelwe."</string> <string name="CLIRPermanent" msgid="166443681876381118">"Ngeke ukwazi ukuguqul izilungiselelo zemininingwane yoshayayo."</string> + <!-- no translation found for auto_data_switch_title (3286350716870518297) --> + <skip /> + <!-- no translation found for auto_data_switch_content (803557715007110959) --> + <skip /> <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ayikho isevisi yedatha yeselula"</string> <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ukushaya okuphuthumayo akutholakali"</string> <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ayikho isevisi yezwi"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 23dd1b407b00..c122e68e360d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4425,13 +4425,13 @@ <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string> <!-- Corner radius of system dialogs --> - <dimen name="config_dialogCornerRadius">2dp</dimen> + <dimen name="config_dialogCornerRadius">28dp</dimen> <!-- Corner radius of system buttons --> - <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen> + <dimen name="config_buttonCornerRadius">4dp</dimen> <!-- Corner radius for bottom sheet system dialogs --> - <dimen name="config_bottomDialogCornerRadius">@dimen/config_dialogCornerRadius</dimen> + <dimen name="config_bottomDialogCornerRadius">16dp</dimen> <!-- Corner radius of system progress bars --> - <dimen name="config_progressBarCornerRadius">@dimen/progress_bar_corner_material</dimen> + <dimen name="config_progressBarCornerRadius">1000dp</dimen> <!-- Controls whether system buttons use all caps for text --> <bool name="config_buttonTextAllCaps">true</bool> <!-- Name of the font family used for system surfaces where the font should use medium weight --> @@ -4945,6 +4945,11 @@ <!-- If face auth sends the user directly to home/last open app, or stays on keyguard --> <bool name="config_faceAuthDismissesKeyguard">true</bool> + <!-- Default value for whether a SFPS device is required to be + {@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth + to unlock the device. --> + <bool name="config_requireScreenOnToAuthEnabled">false</bool> + <!-- The component name for the default profile supervisor, which can be set as a profile owner even after user setup is complete. The defined component should be used for supervision purposes only. The component must be part of a system app. --> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 1997261af879..9dbb6a05e98a 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -979,9 +979,9 @@ <dimen name="controls_thumbnail_image_max_width">280dp</dimen> <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. --> - <dimen name="system_app_widget_background_radius">16dp</dimen> + <dimen name="system_app_widget_background_radius">28dp</dimen> <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. --> - <dimen name="system_app_widget_inner_radius">8dp</dimen> + <dimen name="system_app_widget_inner_radius">20dp</dimen> <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed --> <dimen name="__removed_system_app_widget_internal_padding">16dp</dimen> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 509de3364f0e..1f459c6f623d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1933,6 +1933,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> <string name="permdesc_readMediaImages">Allows the app to read image files from your shared storage.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> + <string name="permlab_readVisualUserSelect">read user selected image and video files from shared storage</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] --> + <string name="permdesc_readVisualUserSelect">Allows the app to read image and video files that you select from your shared storage.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 476d36d0c207..9e3235b885ab 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2725,6 +2725,7 @@ <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" /> <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" /> <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" /> + <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" /> <!-- Face config --> <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" /> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java index 57b9cb1a9097..5bd018bea1d1 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java @@ -23,11 +23,13 @@ import static org.junit.Assert.assertThrows; import android.annotation.Nullable; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; +import android.os.Parcel; import org.junit.Test; public final class ProgramSelectorTest { + private static final int CREATOR_ARRAY_SIZE = 2; private static final int FM_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_FM; private static final int DAB_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_DAB; private static final long FM_FREQUENCY = 88500; @@ -97,6 +99,33 @@ public final class ProgramSelectorTest { } @Test + public void describeContents_forIdentifier() { + assertWithMessage("FM identifier contents") + .that(FM_IDENTIFIER.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forIdentifierCreator() { + ProgramSelector.Identifier[] identifiers = + ProgramSelector.Identifier.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Identifiers").that(identifiers).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test + public void writeToParcel_forIdentifier() { + Parcel parcel = Parcel.obtain(); + + FM_IDENTIFIER.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + ProgramSelector.Identifier identifierFromParcel = + ProgramSelector.Identifier.CREATOR.createFromParcel(parcel); + assertWithMessage("Identifier created from parcel") + .that(identifierFromParcel).isEqualTo(FM_IDENTIFIER); + } + + @Test public void getProgramType() { ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null); @@ -394,6 +423,34 @@ public final class ProgramSelectorTest { .that(selector1.strictEquals(selector2)).isTrue(); } + @Test + public void describeContents_forProgramSelector() { + assertWithMessage("FM selector contents") + .that(getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null) + .describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forProgramSelectorCreator() { + ProgramSelector[] programSelectors = ProgramSelector.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Program selectors").that(programSelectors).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test + public void writeToParcel_forProgramSelector() { + ProgramSelector selectorExpected = + getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null); + Parcel parcel = Parcel.obtain(); + + selectorExpected.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + ProgramSelector selectorFromParcel = ProgramSelector.CREATOR.createFromParcel(parcel); + assertWithMessage("Program selector created from parcel") + .that(selectorFromParcel).isEqualTo(selectorExpected); + } + private ProgramSelector getFmSelector(@Nullable ProgramSelector.Identifier[] secondaryIds, @Nullable long[] vendorIds) { return new ProgramSelector(FM_PROGRAM_TYPE, FM_IDENTIFIER, secondaryIds, vendorIds); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java index 42143b92e9d8..6e1bb4b4c5a3 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertThrows; import android.hardware.radio.Announcement; import android.hardware.radio.ProgramSelector; +import android.os.Parcel; import android.util.ArrayMap; import org.junit.Test; @@ -83,4 +84,35 @@ public final class RadioAnnouncementTest { vendorInfo.put("vendorKeyMock", "vendorValueMock"); return vendorInfo; } + + @Test + public void describeContents_forAnnouncement() { + assertWithMessage("Radio announcement contents") + .that(TEST_ANNOUNCEMENT.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forAnnouncementCreator() { + int sizeExpected = 2; + + Announcement[] announcements = Announcement.CREATOR.newArray(sizeExpected); + + assertWithMessage("Announcements").that(announcements).hasLength(sizeExpected); + } + + @Test + public void writeToParcel_forAnnouncement() { + Parcel parcel = Parcel.obtain(); + + TEST_ANNOUNCEMENT.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + Announcement announcementFromParcel = Announcement.CREATOR.createFromParcel(parcel); + assertWithMessage("Selector of announcement created from parcel") + .that(announcementFromParcel.getSelector()).isEqualTo(FM_PROGRAM_SELECTOR); + assertWithMessage("Type of announcement created from parcel") + .that(announcementFromParcel.getType()).isEqualTo(TRAFFIC_ANNOUNCEMENT_TYPE); + assertWithMessage("Vendor info of announcement created from parcel") + .that(announcementFromParcel.getVendorInfo()).isEqualTo(VENDOR_INFO); + } } diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java index be4d0d434d79..f838a5df2eae 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java @@ -33,6 +33,7 @@ import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.RadioTuner; +import android.os.Parcel; import android.os.RemoteException; import android.util.ArrayMap; @@ -80,6 +81,8 @@ public final class RadioManagerTest { private static final int[] SUPPORTED_IDENTIFIERS_TYPES = new int[]{ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI}; + private static final int CREATOR_ARRAY_SIZE = 3; + private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR = createFmBandDescriptor(); private static final RadioManager.AmBandDescriptor AM_BAND_DESCRIPTOR = @@ -173,6 +176,22 @@ public final class RadioManagerTest { } @Test + public void describeContents_forBandDescriptor() { + RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor(); + + assertWithMessage("Band Descriptor contents") + .that(bandDescriptor.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forBandDescriptorCreator() { + RadioManager.BandDescriptor[] bandDescriptors = + RadioManager.BandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Band Descriptors").that(bandDescriptors).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void isAmBand_forAmBandDescriptor_returnsTrue() { RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor(); @@ -219,18 +238,73 @@ public final class RadioManagerTest { } @Test + public void describeContents_forFmBandDescriptor() { + assertWithMessage("FM Band Descriptor contents") + .that(FM_BAND_DESCRIPTOR.describeContents()).isEqualTo(0); + } + + @Test + public void writeToParcel_forFmBandDescriptor() { + Parcel parcel = Parcel.obtain(); + + FM_BAND_DESCRIPTOR.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioManager.FmBandDescriptor fmBandDescriptorFromParcel = + RadioManager.FmBandDescriptor.CREATOR.createFromParcel(parcel); + assertWithMessage("FM Band Descriptor created from parcel") + .that(fmBandDescriptorFromParcel).isEqualTo(FM_BAND_DESCRIPTOR); + } + + @Test + public void newArray_forFmBandDescriptorCreator() { + RadioManager.FmBandDescriptor[] fmBandDescriptors = + RadioManager.FmBandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("FM Band Descriptors") + .that(fmBandDescriptors).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void isStereoSupported_forAmBandDescriptor() { assertWithMessage("AM Band Descriptor stereo") .that(AM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED); } @Test + public void describeContents_forAmBandDescriptor() { + assertWithMessage("AM Band Descriptor contents") + .that(AM_BAND_DESCRIPTOR.describeContents()).isEqualTo(0); + } + + @Test + public void writeToParcel_forAmBandDescriptor() { + Parcel parcel = Parcel.obtain(); + + AM_BAND_DESCRIPTOR.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioManager.AmBandDescriptor amBandDescriptorFromParcel = + RadioManager.AmBandDescriptor.CREATOR.createFromParcel(parcel); + assertWithMessage("FM Band Descriptor created from parcel") + .that(amBandDescriptorFromParcel).isEqualTo(AM_BAND_DESCRIPTOR); + } + + @Test + public void newArray_forAmBandDescriptorCreator() { + RadioManager.AmBandDescriptor[] amBandDescriptors = + RadioManager.AmBandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("AM Band Descriptors") + .that(amBandDescriptors).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void equals_withSameFmBandDescriptors_returnsTrue() { - RadioManager.FmBandDescriptor fmBandDescriptor1 = createFmBandDescriptor(); - RadioManager.FmBandDescriptor fmBandDescriptor2 = createFmBandDescriptor(); + RadioManager.FmBandDescriptor fmBandDescriptorCompared = createFmBandDescriptor(); assertWithMessage("The same FM Band Descriptor") - .that(fmBandDescriptor1).isEqualTo(fmBandDescriptor2); + .that(FM_BAND_DESCRIPTOR).isEqualTo(fmBandDescriptorCompared); } @Test @@ -258,6 +332,44 @@ public final class RadioManagerTest { } @Test + public void hashCode_withSameFmBandDescriptors_equals() { + RadioManager.FmBandDescriptor fmBandDescriptorCompared = createFmBandDescriptor(); + + assertWithMessage("Hash code of the same FM Band Descriptor") + .that(fmBandDescriptorCompared.hashCode()).isEqualTo(FM_BAND_DESCRIPTOR.hashCode()); + } + + @Test + public void hashCode_withSameAmBandDescriptors_equals() { + RadioManager.AmBandDescriptor amBandDescriptorCompared = createAmBandDescriptor(); + + assertWithMessage("Hash code of the same AM Band Descriptor") + .that(amBandDescriptorCompared.hashCode()).isEqualTo(AM_BAND_DESCRIPTOR.hashCode()); + } + + @Test + public void hashCode_withFmBandDescriptorsOfDifferentAfSupports_notEquals() { + RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor( + REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING, + STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED); + + assertWithMessage("Hash code of FM Band Descriptor of different spacing") + .that(fmBandDescriptorCompared.hashCode()) + .isNotEqualTo(FM_BAND_DESCRIPTOR.hashCode()); + } + + @Test + public void hashCode_withAmBandDescriptorsOfDifferentSpacings_notEquals() { + RadioManager.AmBandDescriptor amBandDescriptorCompared = + new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT, + AM_UPPER_LIMIT, AM_SPACING * 2, STEREO_SUPPORTED); + + assertWithMessage("Hash code of AM Band Descriptor of different spacing") + .that(amBandDescriptorCompared.hashCode()) + .isNotEqualTo(AM_BAND_DESCRIPTOR.hashCode()); + } + + @Test public void getType_forBandConfig() { RadioManager.BandConfig fmBandConfig = createFmBandConfig(); @@ -298,8 +410,24 @@ public final class RadioManagerTest { } @Test + public void describeContents_forBandConfig() { + RadioManager.BandConfig bandConfig = createFmBandConfig(); + + assertWithMessage("FM Band Config contents") + .that(bandConfig.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forBandConfigCreator() { + RadioManager.BandConfig[] bandConfigs = + RadioManager.BandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Band Configs").that(bandConfigs).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void getStereo_forFmBandConfig() { - assertWithMessage("FM Band Config stereo ") + assertWithMessage("FM Band Config stereo") .that(FM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED); } @@ -328,12 +456,66 @@ public final class RadioManagerTest { } @Test + public void describeContents_forFmBandConfig() { + assertWithMessage("FM Band Config contents") + .that(FM_BAND_CONFIG.describeContents()).isEqualTo(0); + } + + @Test + public void writeToParcel_forFmBandConfig() { + Parcel parcel = Parcel.obtain(); + + FM_BAND_CONFIG.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioManager.FmBandConfig fmBandConfigFromParcel = + RadioManager.FmBandConfig.CREATOR.createFromParcel(parcel); + assertWithMessage("FM Band Config created from parcel") + .that(fmBandConfigFromParcel).isEqualTo(FM_BAND_CONFIG); + } + + @Test + public void newArray_forFmBandConfigCreator() { + RadioManager.FmBandConfig[] fmBandConfigs = + RadioManager.FmBandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("FM Band Configs").that(fmBandConfigs).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void getStereo_forAmBandConfig() { assertWithMessage("AM Band Config stereo") .that(AM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED); } @Test + public void describeContents_forAmBandConfig() { + assertWithMessage("AM Band Config contents") + .that(AM_BAND_CONFIG.describeContents()).isEqualTo(0); + } + + @Test + public void writeToParcel_forAmBandConfig() { + Parcel parcel = Parcel.obtain(); + + AM_BAND_CONFIG.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioManager.AmBandConfig amBandConfigFromParcel = + RadioManager.AmBandConfig.CREATOR.createFromParcel(parcel); + assertWithMessage("AM Band Config created from parcel") + .that(amBandConfigFromParcel).isEqualTo(AM_BAND_CONFIG); + } + + @Test + public void newArray_forAmBandConfigCreator() { + RadioManager.AmBandConfig[] amBandConfigs = + RadioManager.AmBandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("AM Band Configs").that(amBandConfigs).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void equals_withSameFmBandConfigs_returnsTrue() { RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig(); @@ -387,6 +569,43 @@ public final class RadioManagerTest { } @Test + public void hashCode_withSameFmBandConfigs_equals() { + RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig(); + + assertWithMessage("Hash code of the same FM Band Config") + .that(FM_BAND_CONFIG.hashCode()).isEqualTo(fmBandConfigCompared.hashCode()); + } + + @Test + public void hashCode_withSameAmBandConfigs_equals() { + RadioManager.AmBandConfig amBandConfigCompared = createAmBandConfig(); + + assertWithMessage("Hash code of the same AM Band Config") + .that(amBandConfigCompared.hashCode()).isEqualTo(AM_BAND_CONFIG.hashCode()); + } + + @Test + public void hashCode_withFmBandConfigsOfDifferentTypes_notEquals() { + RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig( + new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM_HD, FM_LOWER_LIMIT, + FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, + AF_SUPPORTED, EA_SUPPORTED)); + + assertWithMessage("Hash code of FM Band Config with different type") + .that(fmBandConfigCompared.hashCode()).isNotEqualTo(FM_BAND_CONFIG.hashCode()); + } + + @Test + public void hashCode_withAmBandConfigsOfDifferentStereoSupports_notEquals() { + RadioManager.AmBandConfig amBandConfigCompared = new RadioManager.AmBandConfig( + new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT, + AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED)); + + assertWithMessage("Hash code of AM Band Config with different stereo support") + .that(amBandConfigCompared.hashCode()).isNotEqualTo(AM_BAND_CONFIG.hashCode()); + } + + @Test public void getId_forModuleProperties() { assertWithMessage("Properties id") .that(AMFM_PROPERTIES.getId()).isEqualTo(PROPERTIES_ID); @@ -509,6 +728,12 @@ public final class RadioManagerTest { } @Test + public void describeContents_forModuleProperties() { + assertWithMessage("Module properties contents") + .that(AMFM_PROPERTIES.describeContents()).isEqualTo(0); + } + + @Test public void equals_withSameProperties_returnsTrue() { RadioManager.ModuleProperties propertiesCompared = createAmFmProperties(); @@ -530,6 +755,23 @@ public final class RadioManagerTest { } @Test + public void hashCode_withSameModuleProperties_equals() { + RadioManager.ModuleProperties propertiesCompared = createAmFmProperties(); + + assertWithMessage("Hash code of the same module properties") + .that(propertiesCompared.hashCode()).isEqualTo(AMFM_PROPERTIES.hashCode()); + } + + @Test + public void newArray_forModulePropertiesCreator() { + RadioManager.ModuleProperties[] modulePropertiesArray = + RadioManager.ModuleProperties.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Module properties array") + .that(modulePropertiesArray).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test public void getSelector_forProgramInfo() { assertWithMessage("Selector of DAB program info") .that(DAB_PROGRAM_INFO.getSelector()).isEqualTo(DAB_SELECTOR); @@ -549,7 +791,7 @@ public final class RadioManagerTest { @Test public void getRelatedContent_forProgramInfo() { - assertWithMessage("Related contents of DAB program info") + assertWithMessage("DAB program info contents") .that(DAB_PROGRAM_INFO.getRelatedContent()) .containsExactly(DAB_SID_EXT_IDENTIFIER_RELATED); } @@ -627,6 +869,33 @@ public final class RadioManagerTest { } @Test + public void describeContents_forProgramInfo() { + assertWithMessage("Program info contents") + .that(DAB_PROGRAM_INFO.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forProgramInfoCreator() { + RadioManager.ProgramInfo[] programInfoArray = + RadioManager.ProgramInfo.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Program infos").that(programInfoArray).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test + public void writeToParcel_forProgramInfo() { + Parcel parcel = Parcel.obtain(); + + DAB_PROGRAM_INFO.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioManager.ProgramInfo programInfoFromParcel = + RadioManager.ProgramInfo.CREATOR.createFromParcel(parcel); + assertWithMessage("Program info created from parcel") + .that(programInfoFromParcel).isEqualTo(DAB_PROGRAM_INFO); + } + + @Test public void equals_withSameProgramInfo_returnsTrue() { RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(DAB_SELECTOR); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java index fe15597a9514..5771135e32b8 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java @@ -20,18 +20,63 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; +import android.graphics.Bitmap; import android.hardware.radio.RadioMetadata; +import android.os.Parcel; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import java.util.Set; +@RunWith(MockitoJUnitRunner.class) public final class RadioMetadataTest { + private static final int CREATOR_ARRAY_SIZE = 3; private static final int INT_KEY_VALUE = 1; + private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200; + private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1; private final RadioMetadata.Builder mBuilder = new RadioMetadata.Builder(); + @Mock + private Bitmap mBitmapValue; + + @Test + public void describeContents_forClock() { + RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH, + TEST_TIME_ZONE_OFFSET_MINUTES); + + assertWithMessage("Describe contents for metadata clock") + .that(clock.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forClockCreator() { + RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE); + } + + @Test + public void writeToParcel_forClock() { + RadioMetadata.Clock clockExpected = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH, + TEST_TIME_ZONE_OFFSET_MINUTES); + Parcel parcel = Parcel.obtain(); + + clockExpected.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel); + assertWithMessage("UTC second since epoch of metadata clock created from parcel") + .that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH); + assertWithMessage("Time zone offset minutes of metadata clock created from parcel") + .that(clockFromParcel.getTimezoneOffsetMinutes()) + .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES); + } + @Test public void putString_withIllegalKey() { String invalidStringKey = RadioMetadata.METADATA_KEY_RDS_PI; @@ -129,22 +174,56 @@ public final class RadioMetadataTest { } @Test + public void getBitmap_withKeyInMetadata() { + String key = RadioMetadata.METADATA_KEY_ICON; + RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build(); + + assertWithMessage("Bitmap value for key %s in metadata", key) + .that(metadata.getBitmap(key)).isEqualTo(mBitmapValue); + } + + @Test + public void getBitmap_withKeyNotInMetadata() { + String key = RadioMetadata.METADATA_KEY_ICON; + RadioMetadata metadata = mBuilder.build(); + + assertWithMessage("Bitmap value for key %s not in metadata", key) + .that(metadata.getBitmap(key)).isNull(); + } + + @Test + public void getBitmapId_withKeyInMetadata() { + String key = RadioMetadata.METADATA_KEY_ART; + RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build(); + + assertWithMessage("Bitmap id value for key %s in metadata", key) + .that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE); + } + + @Test + public void getBitmapId_withKeyNotInMetadata() { + String key = RadioMetadata.METADATA_KEY_ART; + RadioMetadata metadata = mBuilder.build(); + + assertWithMessage("Bitmap id value for key %s not in metadata", key) + .that(metadata.getBitmapId(key)).isEqualTo(0); + } + + @Test public void getClock_withKeyInMetadata() { String key = RadioMetadata.METADATA_KEY_CLOCK; - long utcSecondsSinceEpochExpected = 200; - int timezoneOffsetMinutesExpected = 1; RadioMetadata metadata = mBuilder - .putClock(key, utcSecondsSinceEpochExpected, timezoneOffsetMinutesExpected) + .putClock(key, TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES) .build(); RadioMetadata.Clock clockExpected = metadata.getClock(key); assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key) .that(clockExpected.getUtcEpochSeconds()) - .isEqualTo(utcSecondsSinceEpochExpected); + .isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH); assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key) .that(clockExpected.getTimezoneOffsetMinutes()) - .isEqualTo(timezoneOffsetMinutesExpected); + .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES); } @Test @@ -180,12 +259,13 @@ public final class RadioMetadataTest { RadioMetadata metadata = mBuilder .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue) .build(); Set<String> metadataSet = metadata.keySet(); assertWithMessage("Metadata set of non-empty metadata") - .that(metadataSet).containsExactly( + .that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON, RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST); } @@ -208,4 +288,46 @@ public final class RadioMetadataTest { .that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI); } + @Test + public void equals_forMetadataWithSameContents_returnsTrue() { + RadioMetadata metadata = mBuilder + .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) + .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .build(); + RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata); + RadioMetadata metadataCopied = copyBuilder.build(); + + assertWithMessage("Metadata with the same contents") + .that(metadataCopied).isEqualTo(metadata); + } + + @Test + public void describeContents_forMetadata() { + RadioMetadata metadata = mBuilder.build(); + + assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0); + } + + @Test + public void newArray_forRadioMetadataCreator() { + RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE); + + assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE); + } + + @Test + public void writeToParcel_forRadioMetadata() { + RadioMetadata metadataExpected = mBuilder + .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE) + .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest") + .build(); + Parcel parcel = Parcel.obtain(); + + metadataExpected.writeToParcel(parcel, /* flags= */ 0); + parcel.setDataPosition(0); + + RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel); + assertWithMessage("Radio metadata created from parcel") + .that(metadataFromParcel).isEqualTo(metadataExpected); + } } diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java index 0f81896692c0..7e875ada7267 100644 --- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java +++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java @@ -18,6 +18,8 @@ package android.app.activity; import android.app.Activity; import android.app.ActivityManager; +import android.app.BroadcastOptions; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -536,4 +538,40 @@ public class BroadcastTest extends ActivityTestsBase { Log.i("foo", "Unregister exception", e); } } + + public void testBroadcastOption_interactive() throws Exception { + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setInteractiveBroadcast(true); + final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED); + + try { + getContext().sendBroadcast(intent, null, options.toBundle()); + fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + } catch (SecurityException se) { + // Expected, correct behavior - this case intentionally empty + } catch (Exception e) { + fail("Unexpected exception " + e.getMessage() + + " thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + } + } + + public void testBroadcastOption_interactive_PendingIntent() throws Exception { + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setInteractiveBroadcast(true); + final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED); + PendingIntent brPending = PendingIntent.getBroadcast(getContext(), + 1, intent, PendingIntent.FLAG_IMMUTABLE); + + try { + brPending.send(getContext(), 1, null, null, null, null, options.toBundle()); + fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + } catch (SecurityException se) { + // Expected, correct behavior - this case intentionally empty + } catch (Exception e) { + fail("Unexpected exception " + e.getMessage() + + " thrown with BroadcastOptions.setInteractiveBroadcast(true)"); + } finally { + brPending.cancel(); + } + } } diff --git a/core/tests/coretests/src/android/app/activity/OWNERS b/core/tests/coretests/src/android/app/activity/OWNERS index 0862c05e0ee4..7e24aef076d1 100644 --- a/core/tests/coretests/src/android/app/activity/OWNERS +++ b/core/tests/coretests/src/android/app/activity/OWNERS @@ -1 +1,2 @@ include /services/core/java/com/android/server/wm/OWNERS +include /services/core/java/com/android/server/am/OWNERS diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java index bb1a3b182f91..ee1e10f9009e 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.IAccessibilityServiceClient; import android.app.Instrumentation; import android.app.PendingIntent; import android.app.RemoteAction; @@ -34,6 +35,7 @@ import android.content.Intent; import android.graphics.drawable.Icon; import android.os.UserHandle; +import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -51,6 +53,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Executors; /** * Tests for the AccessibilityManager by mocking the backing service. @@ -70,6 +73,7 @@ public class AccessibilityManagerTest { LABEL, DESCRIPTION, TEST_PENDING_INTENT); + private static final int DISPLAY_ID = 22; @Mock private IAccessibilityManager mMockService; private MessageCapturingHandler mHandler; @@ -224,4 +228,45 @@ public class AccessibilityManagerTest { assertEquals(mFocusColorDefaultValue, manager.getAccessibilityFocusColor()); } + + @Test + public void testRegisterAccessibilityProxy() throws Exception { + // Accessibility does not need to be enabled for a proxy to be registered. + AccessibilityManager manager = + new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService, + UserHandle.USER_CURRENT, true); + + + ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>(); + infos.add(new AccessibilityServiceInfo()); + AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos); + manager.registerDisplayProxy(proxy); + // Cannot access proxy.mServiceClient directly due to visibility. + verify(mMockService).registerProxyForDisplay(any(IAccessibilityServiceClient.class), + any(Integer.class)); + } + + @Test + public void testUnregisterAccessibilityProxy() throws Exception { + // Accessibility does not need to be enabled for a proxy to be registered. + final AccessibilityManager manager = + new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService, + UserHandle.USER_CURRENT, true); + + final ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>(); + infos.add(new AccessibilityServiceInfo()); + + final AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos); + manager.registerDisplayProxy(proxy); + manager.unregisterDisplayProxy(proxy); + verify(mMockService).unregisterProxyForDisplay(proxy.getDisplayId()); + } + + private class MyAccessibilityProxy extends AccessibilityDisplayProxy { + // TODO(241429275): Will override A11yProxy methods in the future. + MyAccessibilityProxy(int displayId, + @NonNull List<AccessibilityServiceInfo> serviceInfos) { + super(displayId, Executors.newSingleThreadExecutor(), serviceInfos); + } + } } diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java new file mode 100644 index 000000000000..79aeaa39bc7c --- /dev/null +++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import android.annotation.NonNull; +import android.graphics.PointF; +import android.graphics.RectF; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class ParcelableHandwritingGestureTest { + + @Test + public void testCreationFailWithNullPointerException() { + assertThrows(NullPointerException.class, () -> ParcelableHandwritingGesture.of(null)); + } + + @Test + public void testInvalidTypeHeader() { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + // GESTURE_TYPE_NONE is not a supported header. + parcel.writeInt(HandwritingGesture.GESTURE_TYPE_NONE); + final Parcel initializedParcel = parcel; + assertThrows(UnsupportedOperationException.class, + () -> ParcelableHandwritingGesture.CREATOR.createFromParcel(initializedParcel)); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } + + @Test + public void testSelectGesture() { + verifyEqualityAfterUnparcel(new SelectGesture.Builder() + .setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setSelectionArea(new RectF(1, 2, 3, 4)) + .setFallbackText("") + .build()); + } + + @Test + public void testSelectRangeGesture() { + verifyEqualityAfterUnparcel(new SelectRangeGesture.Builder() + .setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setSelectionStartArea(new RectF(1, 2, 3, 4)) + .setSelectionEndArea(new RectF(5, 6, 7, 8)) + .setFallbackText("") + .build()); + } + + @Test + public void testInsertGestureGesture() { + verifyEqualityAfterUnparcel(new InsertGesture.Builder() + .setTextToInsert("text") + .setInsertionPoint(new PointF(1, 1)).setFallbackText("") + .build()); + } + + @Test + public void testDeleteGestureGesture() { + verifyEqualityAfterUnparcel(new DeleteGesture.Builder() + .setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setDeletionArea(new RectF(1, 2, 3, 4)) + .setFallbackText("") + .build()); + } + + @Test + public void testDeleteRangeGestureGesture() { + verifyEqualityAfterUnparcel(new DeleteRangeGesture.Builder() + .setGranularity(HandwritingGesture.GRANULARITY_WORD) + .setDeletionStartArea(new RectF(1, 2, 3, 4)) + .setDeletionEndArea(new RectF(5, 6, 7, 8)) + .setFallbackText("") + .build()); + } + + @Test + public void testRemoveSpaceGestureGesture() { + verifyEqualityAfterUnparcel(new RemoveSpaceGesture.Builder() + .setPoints(new PointF(1f, 2f), new PointF(3f, 4f)) + .setFallbackText("") + .build()); + } + + @Test + public void testJoinOrSplitGestureGesture() { + verifyEqualityAfterUnparcel(new JoinOrSplitGesture.Builder() + .setJoinOrSplitPoint(new PointF(1f, 2f)) + .setFallbackText("") + .build()); + } + + static void verifyEqualityAfterUnparcel(@NonNull HandwritingGesture gesture) { + assertEquals(gesture, cloneViaParcel(ParcelableHandwritingGesture.of(gesture)).get()); + } + + private static ParcelableHandwritingGesture cloneViaParcel( + @NonNull ParcelableHandwritingGesture original) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return ParcelableHandwritingGesture.CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } +} diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index d4a663221cd7..95aa5d0de119 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -32,6 +32,7 @@ import android.app.Instrumentation; import android.content.Context; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.view.HandwritingDelegateConfiguration; import android.view.HandwritingInitiator; import android.view.InputDevice; import android.view.MotionEvent; @@ -208,6 +209,30 @@ public class HandwritingInitiatorTest { } @Test + public void onTouchEvent_startHandwriting_delegate() { + int delegatorViewId = 234; + View delegatorView = new View(mContext); + delegatorView.setId(delegatorViewId); + + mTestView.setHandwritingDelegateConfiguration( + new HandwritingDelegateConfiguration( + delegatorViewId, + () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView))); + + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView); + } + + @Test public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() { final Rect rect = new Rect(600, 600, 900, 900); final View testView = createView(rect, true /* autoHandwritingEnabled */, diff --git a/core/tests/coretests/src/com/android/internal/security/OWNERS b/core/tests/coretests/src/com/android/internal/security/OWNERS new file mode 100644 index 000000000000..4f4d8d7a0932 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/security/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 36824 + +per-file VerityUtilsTest.java = file:platform/system/security:/fsverity/OWNERS diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java index 0f82c8fe904a..889edb3502c4 100644 --- a/graphics/java/android/view/PixelCopy.java +++ b/graphics/java/android/view/PixelCopy.java @@ -311,11 +311,11 @@ public final class PixelCopy { /** * Contains the result of a PixelCopy request */ - public static final class CopyResult { + public static final class Result { private int mStatus; private Bitmap mBitmap; - private CopyResult(@CopyResultStatus int status, Bitmap bitmap) { + private Result(@CopyResultStatus int status, Bitmap bitmap) { mStatus = status; mBitmap = bitmap; } @@ -335,8 +335,8 @@ public final class PixelCopy { /** * If the PixelCopy {@link Request} was given a destination bitmap with - * {@link Request#setDestinationBitmap(Bitmap)} then the returned bitmap will be the same - * as the one given. If no destination bitmap was provided, then this + * {@link Request.Builder#setDestinationBitmap(Bitmap)} then the returned bitmap will be + * the same as the one given. If no destination bitmap was provided, then this * will contain the automatically allocated Bitmap to hold the result. * * @return the Bitmap the copy request was stored in. @@ -349,66 +349,199 @@ public final class PixelCopy { } /** - * A builder to create the complete PixelCopy request, which is then executed by calling - * {@link #request()} + * Represents a PixelCopy request. + * + * To create a copy request, use either of the PixelCopy.Request.ofWindow or + * PixelCopy.Request.ofSurface factories to create a {@link Request.Builder} for the + * given source content. After setting any optional parameters, such as + * {@link Builder#setSourceRect(Rect)}, build the request with {@link Builder#build()} and + * then execute it with {@link PixelCopy#request(Request)} */ public static final class Request { + private final Surface mSource; + private final Consumer<Result> mListener; + private final Executor mListenerThread; + private final Rect mSourceInsets; + private Rect mSrcRect; + private Bitmap mDest; + private Request(Surface source, Rect sourceInsets, Executor listenerThread, - Consumer<CopyResult> listener) { + Consumer<Result> listener) { this.mSource = source; this.mSourceInsets = sourceInsets; this.mListenerThread = listenerThread; this.mListener = listener; } - private final Surface mSource; - private final Consumer<CopyResult> mListener; - private final Executor mListenerThread; - private final Rect mSourceInsets; - private Rect mSrcRect; - private Bitmap mDest; + /** + * A builder to create the complete PixelCopy request, which is then executed by calling + * {@link #request(Request)} with the built request returned from {@link #build()} + */ + public static final class Builder { + private Request mRequest; + + private Builder(Request request) { + mRequest = request; + } + + private void requireNotBuilt() { + if (mRequest == null) { + throw new IllegalStateException("build() already called on this builder"); + } + } + + /** + * Sets the region of the source to copy from. By default, the entire source is copied + * to the output. If only a subset of the source is necessary to be copied, specifying + * a srcRect will improve performance by reducing + * the amount of data being copied. + * + * @param srcRect The area of the source to read from. Null or empty will be treated to + * mean the entire source + * @return this + */ + public @NonNull Builder setSourceRect(@Nullable Rect srcRect) { + requireNotBuilt(); + mRequest.mSrcRect = srcRect; + return this; + } + + /** + * Specifies the output bitmap in which to store the result. By default, a Bitmap of + * format {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height + * matching that of the {@link #setSourceRect(Rect) source area} will be created to + * place the result. + * + * @param destination The bitmap to store the result, or null to have a bitmap + * automatically created of the appropriate size. If not null, must + * not be {@link Bitmap#isRecycled() recycled} and must be + * {@link Bitmap#isMutable() mutable}. + * @return this + */ + public @NonNull Builder setDestinationBitmap(@Nullable Bitmap destination) { + requireNotBuilt(); + if (destination != null) { + validateBitmapDest(destination); + } + mRequest.mDest = destination; + return this; + } + + /** + * @return The built {@link PixelCopy.Request} + */ + public @NonNull Request build() { + requireNotBuilt(); + Request ret = mRequest; + mRequest = null; + return ret; + } + } /** - * Sets the region of the source to copy from. By default, the entire source is copied to - * the output. If only a subset of the source is necessary to be copied, specifying a - * srcRect will improve performance by reducing - * the amount of data being copied. + * Creates a PixelCopy request for the given {@link Window} + * @param source The Window to copy from + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Builder} builder to set the optional params & execute the request + */ + public static @NonNull Builder ofWindow(@NonNull Window source, + @NonNull Executor callbackExecutor, + @NonNull Consumer<Result> listener) { + final Rect insets = new Rect(); + final Surface surface = sourceForWindow(source, insets); + return new Builder(new Request(surface, insets, callbackExecutor, listener)); + } + + /** + * Creates a PixelCopy request for the {@link Window} that the given {@link View} is + * attached to. + * + * Note that this copy request is not cropped to the area the View occupies by default. If + * that behavior is desired, use {@link View#getLocationInWindow(int[])} combined with + * {@link Builder#setSourceRect(Rect)} to set a crop area to restrict the copy operation. * - * @param srcRect The area of the source to read from. Null or empty will be treated to - * mean the entire source - * @return this + * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that + * will be used to retrieve the window to copy from. + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Builder} builder to set the optional params & execute the request */ - public @NonNull Request setSourceRect(@Nullable Rect srcRect) { - this.mSrcRect = srcRect; - return this; + public static @NonNull Builder ofWindow(@NonNull View source, + @NonNull Executor callbackExecutor, + @NonNull Consumer<Result> listener) { + if (source == null || !source.isAttachedToWindow()) { + throw new IllegalArgumentException( + "View must not be null & must be attached to window"); + } + final Rect insets = new Rect(); + Surface surface = null; + final ViewRootImpl root = source.getViewRootImpl(); + if (root != null) { + surface = root.mSurface; + insets.set(root.mWindowAttributes.surfaceInsets); + } + if (surface == null || !surface.isValid()) { + throw new IllegalArgumentException( + "Window doesn't have a backing surface!"); + } + return new Builder(new Request(surface, insets, callbackExecutor, listener)); } /** - * Specifies the output bitmap in which to store the result. By default, a Bitmap of format - * {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height matching that - * of the {@link #setSourceRect(Rect) source area} will be created to place the result. + * Creates a PixelCopy request for the given {@link Surface} * - * @param destination The bitmap to store the result, or null to have a bitmap - * automatically created of the appropriate size. If not null, must not - * be {@link Bitmap#isRecycled() recycled} and must be - * {@link Bitmap#isMutable() mutable}. - * @return this + * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}. + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Builder} builder to set the optional params & execute the request */ - public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) { - if (destination != null) { - validateBitmapDest(destination); + public static @NonNull Builder ofSurface(@NonNull Surface source, + @NonNull Executor callbackExecutor, + @NonNull Consumer<Result> listener) { + if (source == null || !source.isValid()) { + throw new IllegalArgumentException("Source must not be null & must be valid"); } - this.mDest = destination; - return this; + return new Builder(new Request(source, null, callbackExecutor, listener)); + } + + /** + * Creates a PixelCopy request for the {@link Surface} belonging to the + * given {@link SurfaceView} + * + * @param source The SurfaceView to copy from. The backing surface must be + * {@link Surface#isValid() valid} + * @param callbackExecutor The executor to run the callback on + * @param listener The callback for when the copy request is completed + * @return A {@link Builder} builder to set the optional params & execute the request + */ + public static @NonNull Builder ofSurface(@NonNull SurfaceView source, + @NonNull Executor callbackExecutor, + @NonNull Consumer<Result> listener) { + return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener); + } + + /** + * @return The destination bitmap as set by {@link Builder#setDestinationBitmap(Bitmap)} + */ + public @Nullable Bitmap getDestinationBitmap() { + return mDest; } /** - * Executes the request. + * @return The source rect to copy from as set by {@link Builder#setSourceRect(Rect)} + */ + public @Nullable Rect getSourceRect() { + return mSrcRect; + } + + /** + * @hide */ public void request() { if (!mSource.isValid()) { mListenerThread.execute(() -> mListener.accept( - new CopyResult(ERROR_SOURCE_INVALID, null))); + new Result(ERROR_SOURCE_INVALID, null))); return; } HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest( @@ -416,93 +549,18 @@ public final class PixelCopy { @Override public void onCopyFinished(int result) { mListenerThread.execute(() -> mListener.accept( - new CopyResult(result, mDestinationBitmap))); + new Result(result, mDestinationBitmap))); } }); } } /** - * Creates a PixelCopy request for the given {@link Window} - * @param source The Window to copy from - * @param callbackExecutor The executor to run the callback on - * @param listener The callback for when the copy request is completed - * @return A {@link Request} builder to set the optional params & execute the request - */ - public static @NonNull Request ofWindow(@NonNull Window source, - @NonNull Executor callbackExecutor, - @NonNull Consumer<CopyResult> listener) { - final Rect insets = new Rect(); - final Surface surface = sourceForWindow(source, insets); - return new Request(surface, insets, callbackExecutor, listener); - } - - /** - * Creates a PixelCopy request for the {@link Window} that the given {@link View} is - * attached to. - * - * Note that this copy request is not cropped to the area the View occupies by default. If that - * behavior is desired, use {@link View#getLocationInWindow(int[])} combined with - * {@link Request#setSourceRect(Rect)} to set a crop area to restrict the copy operation. - * - * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that - * will be used to retrieve the window to copy from. - * @param callbackExecutor The executor to run the callback on - * @param listener The callback for when the copy request is completed - * @return A {@link Request} builder to set the optional params & execute the request - */ - public static @NonNull Request ofWindow(@NonNull View source, - @NonNull Executor callbackExecutor, - @NonNull Consumer<CopyResult> listener) { - if (source == null || !source.isAttachedToWindow()) { - throw new IllegalArgumentException( - "View must not be null & must be attached to window"); - } - final Rect insets = new Rect(); - Surface surface = null; - final ViewRootImpl root = source.getViewRootImpl(); - if (root != null) { - surface = root.mSurface; - insets.set(root.mWindowAttributes.surfaceInsets); - } - if (surface == null || !surface.isValid()) { - throw new IllegalArgumentException( - "Window doesn't have a backing surface!"); - } - return new Request(surface, insets, callbackExecutor, listener); - } - - /** - * Creates a PixelCopy request for the given {@link Surface} - * - * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}. - * @param callbackExecutor The executor to run the callback on - * @param listener The callback for when the copy request is completed - * @return A {@link Request} builder to set the optional params & execute the request - */ - public static @NonNull Request ofSurface(@NonNull Surface source, - @NonNull Executor callbackExecutor, - @NonNull Consumer<CopyResult> listener) { - if (source == null || !source.isValid()) { - throw new IllegalArgumentException("Source must not be null & must be valid"); - } - return new Request(source, null, callbackExecutor, listener); - } - - /** - * Creates a PixelCopy request for the {@link Surface} belonging to the - * given {@link SurfaceView} - * - * @param source The SurfaceView to copy from. The backing surface must be - * {@link Surface#isValid() valid} - * @param callbackExecutor The executor to run the callback on - * @param listener The callback for when the copy request is completed - * @return A {@link Request} builder to set the optional params & execute the request + * Executes the pixel copy request + * @param request The request to execute */ - public static @NonNull Request ofSurface(@NonNull SurfaceView source, - @NonNull Executor callbackExecutor, - @NonNull Consumer<CopyResult> listener) { - return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener); + public static void request(@NonNull Request request) { + request.request(); } private PixelCopy() {} diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml new file mode 100644 index 000000000000..8779cc09715b --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" +> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="6.0" + android:translateY="6.0"> + <path + android:fillColor="@android:color/black" + android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml new file mode 100644 index 000000000000..ea0fbb0e5d33 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" +> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="6.0" + android:translateY="6.0"> + <path + android:fillColor="@android:color/black" + android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml new file mode 100644 index 000000000000..c55cbe2d054c --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" +> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="6.0" + android:translateY="6.0"> + <path + android:fillColor="@android:color/black" + android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml new file mode 100644 index 000000000000..447df43dfddd --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.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. + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" +> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="6.0" + android:translateY="6.0"> + <path + android:fillColor="@android:color/black" + android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml new file mode 100644 index 000000000000..c334a543a86a --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_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="32.0dp" + android:height="32.0dp" + android:viewportWidth="32.0" + android:viewportHeight="32.0" +> + <group android:translateX="6.0" + android:translateY="8.0"> + <path + android:fillColor="@android:color/black" + android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml new file mode 100644 index 000000000000..e307f007e4a4 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml @@ -0,0 +1,30 @@ +<?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="210.0dp" + android:height="64.0dp" + android:tint="@color/decor_button_light_color" +> + <group android:scaleX="0.5" + android:scaleY="0.5" + android:translateX="8.0" + android:translateY="8.0" > + <path + android:fillColor="@android:color/white" + android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml new file mode 100644 index 000000000000..d9a140b810f8 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="utf-8"?> + <!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.wm.shell.windowdecor.WindowDecorLinearLayout +xmlns:android="http://schemas.android.com/apk/res/android" +android:id="@+id/handle_menu" +android:layout_width="wrap_content" +android:layout_height="wrap_content" +android:gravity="center_horizontal" +android:background="@drawable/decor_caption_title"> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/fullscreen_button" + android:contentDescription="@string/fullscreen_text" + android:background="@drawable/caption_fullscreen_button"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/split_screen_button" + android:contentDescription="@string/split_screen_text" + android:background="@drawable/caption_split_screen_button"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/floating_button" + android:contentDescription="@string/float_button_text" + android:background="@drawable/caption_floating_button"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/desktop_button" + android:contentDescription="@string/desktop_text" + android:background="@drawable/caption_desktop_button"/> + <Button + style="@style/CaptionButtonStyle" + android:id="@+id/more_button" + android:contentDescription="@string/more_button_text" + android:background="@drawable/caption_more_button"/> +</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml index 38cd5702f134..51e634c17532 100644 --- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml +++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml @@ -19,14 +19,10 @@ android:id="@+id/caption" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center_horizontal" android:background="@drawable/decor_caption_title"> <Button + style="@style/CaptionButtonStyle" android:id="@+id/back_button" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" android:contentDescription="@string/back_button_text" android:background="@drawable/decor_back_button_dark" /> @@ -39,11 +35,8 @@ android:contentDescription="@string/handle_text" android:background="@drawable/decor_handle_dark"/> <Button + style="@style/CaptionButtonStyle" android:id="@+id/close_window" - android:layout_width="32dp" - android:layout_height="32dp" - android:layout_margin="5dp" - android:padding="4dp" android:contentDescription="@string/close_button_text" android:background="@drawable/decor_close_button_dark"/> </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 4807f08b4bed..097a567c6b43 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -202,4 +202,14 @@ <string name="back_button_text">Back</string> <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] --> <string name="handle_text">Handle</string> + <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] --> + <string name="fullscreen_text">Fullscreen</string> + <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] --> + <string name="desktop_text">Desktop Mode</string> + <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] --> + <string name="split_screen_text">Split Screen</string> + <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] --> + <string name="more_button_text">More</string> + <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] --> + <string name="float_button_text">Float</string> </resources> diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml index 19f7c3ef4364..a8597210d72e 100644 --- a/libs/WindowManager/Shell/res/values/styles.xml +++ b/libs/WindowManager/Shell/res/values/styles.xml @@ -30,6 +30,13 @@ <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item> </style> + <style name="CaptionButtonStyle"> + <item name="android:layout_width">32dp</item> + <item name="android:layout_height">32dp</item> + <item name="android:layout_margin">5dp</item> + <item name="android:padding">4dp</item> + </style> + <style name="DockedDividerBackground"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">@dimen/split_divider_bar_width</item> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS new file mode 100644 index 000000000000..7237d2bde39f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS @@ -0,0 +1,2 @@ +# WM shell sub-modules splitscreen owner +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index c2ad1a98d167..295a2e3c4244 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -69,6 +69,7 @@ import com.android.wm.shell.common.InteractionJankMonitorUtils; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import java.io.PrintWriter; +import java.util.function.Consumer; /** * Records and handles layout of splits. Helps to calculate proper bounds when configuration or @@ -85,9 +86,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private static final int FLING_ENTER_DURATION = 350; private static final int FLING_EXIT_DURATION = 350; - private final int mDividerWindowWidth; - private final int mDividerInsets; - private final int mDividerSize; + private int mDividerWindowWidth; + private int mDividerInsets; + private int mDividerSize; private final Rect mTempRect = new Rect(); private final Rect mRootBounds = new Rect(); @@ -130,6 +131,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mContext = context.createConfigurationContext(configuration); mOrientation = configuration.orientation; mRotation = configuration.windowConfiguration.getRotation(); + mDensity = configuration.densityDpi; mSplitLayoutHandler = splitLayoutHandler; mDisplayImeController = displayImeController; mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration, @@ -138,24 +140,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId()); mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType); - final Resources resources = context.getResources(); - mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width); - mDividerInsets = getDividerInsets(resources, context.getDisplay()); - mDividerWindowWidth = mDividerSize + 2 * mDividerInsets; + updateDividerConfig(mContext); mRootBounds.set(configuration.windowConfiguration.getBounds()); mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); resetDividerPosition(); - mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide); + mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide); updateInvisibleRect(); } - private int getDividerInsets(Resources resources, Display display) { + private void updateDividerConfig(Context context) { + final Resources resources = context.getResources(); + final Display display = context.getDisplay(); final int dividerInset = resources.getDimensionPixelSize( com.android.internal.R.dimen.docked_stack_divider_insets); - int radius = 0; RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; @@ -166,7 +166,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); radius = corner != null ? Math.max(radius, corner.getRadius()) : radius; - return Math.max(dividerInset, radius); + mDividerInsets = Math.max(dividerInset, radius); + mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width); + mDividerWindowWidth = mDividerSize + 2 * mDividerInsets; } /** Gets bounds of the primary split with screen based coordinate. */ @@ -308,6 +310,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mRotation = rotation; mDensity = density; mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null); + updateDividerConfig(mContext); initDividerPosition(mTempRect); updateInvisibleRect(); @@ -599,7 +602,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange /** Swich both surface position with animation. */ public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1, - SurfaceControl leash2, Runnable finishCallback) { + SurfaceControl leash2, Consumer<Rect> finishCallback) { final boolean isLandscape = isLandscape(); final Rect insets = getDisplayInsets(mContext); insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top, @@ -617,18 +620,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange distBounds1.offset(-mRootBounds.left, -mRootBounds.top); distBounds2.offset(-mRootBounds.left, -mRootBounds.top); distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top); - // DO NOT move to insets area for smooth animation. - distBounds1.set(distBounds1.left, distBounds1.top, - distBounds1.right - insets.right, distBounds1.bottom - insets.bottom); - distBounds2.set(distBounds2.left + insets.left, distBounds2.top + insets.top, - distBounds2.right, distBounds2.bottom); ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1, - false /* alignStart */); + -insets.left, -insets.top); ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2, - true /* alignStart */); + insets.left, insets.top); ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(), - distDividerBounds, true /* alignStart */); + distDividerBounds, 0 /* offsetX */, 0 /* offsetY */); AnimatorSet set = new AnimatorSet(); set.playTogether(animator1, animator2, animator3); @@ -638,14 +636,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void onAnimationEnd(Animator animation) { mDividePosition = dividerPos; updateBounds(mDividePosition); - finishCallback.run(); + finishCallback.accept(insets); } }); set.start(); } private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash, - Rect start, Rect end, boolean alignStart) { + Rect start, Rect end, float offsetX, float offsetY) { Rect tempStart = new Rect(start); Rect tempEnd = new Rect(end); final float diffX = tempEnd.left - tempStart.left; @@ -661,15 +659,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange final float distY = tempStart.top + scale * diffY; final int width = (int) (tempStart.width() + scale * diffWidth); final int height = (int) (tempStart.height() + scale * diffHeight); - if (alignStart) { + if (offsetX == 0 && offsetY == 0) { t.setPosition(leash, distX, distY); t.setWindowCrop(leash, width, height); } else { - final int offsetX = width - tempStart.width(); - final int offsetY = height - tempStart.height(); - t.setPosition(leash, distX + offsetX, distY + offsetY); + final int diffOffsetX = (int) (scale * offsetX); + final int diffOffsetY = (int) (scale * offsetY); + t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY); mTempRect.set(0, 0, width, height); - mTempRect.offsetTo(-offsetX, -offsetY); + mTempRect.offsetTo(-diffOffsetX, -diffOffsetY); t.setCrop(leash, mTempRect); } t.apply(); 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 f1670cd792cf..4459f57a1356 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.IconProvider; +import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.TaskViewTransitions; @@ -271,11 +272,12 @@ public abstract class WMShellModule { TaskStackListenerImpl taskStackListener, UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return OneHandedController.create(context, shellInit, shellCommandHandler, shellController, windowManager, displayController, displayLayout, taskStackListener, jankMonitor, - uiEventLogger, mainExecutor, mainHandler); + uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java index b310ee2095bf..b5ed509b6935 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java @@ -34,6 +34,7 @@ import android.graphics.Rect; import android.os.Binder; import android.util.Slog; import android.view.ContextThemeWrapper; +import android.view.Display; import android.view.IWindow; import android.view.LayoutInflater; import android.view.SurfaceControl; @@ -47,6 +48,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.wm.shell.R; +import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayLayout; import java.io.PrintWriter; @@ -67,11 +69,14 @@ public final class BackgroundWindowManager extends WindowlessWindowManager { private SurfaceControl mLeash; private View mBackgroundView; private @OneHandedState.State int mCurrentState; + private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; - public BackgroundWindowManager(Context context) { + public BackgroundWindowManager(Context context, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer) { super(context.getResources().getConfiguration(), null /* rootSurface */, null /* hostInputToken */); mContext = context; + mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer; mTransactionFactory = SurfaceControl.Transaction::new; } @@ -112,6 +117,7 @@ public final class BackgroundWindowManager extends WindowlessWindowManager { .setOpaque(true) .setName(TAG) .setCallsite("BackgroundWindowManager#attachToParentSurface"); + mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder); mLeash = builder.build(); b.setParent(mLeash); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 679d4ca2ac48..ad135d1baa14 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -47,6 +47,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.UiEventLogger; import com.android.wm.shell.R; +import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; @@ -204,12 +205,14 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger, + RootDisplayAreaOrganizer rootDisplayAreaOrganizer, ShellExecutor mainExecutor, Handler mainHandler) { OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); OneHandedState oneHandedState = new OneHandedState(); - BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context); + BackgroundWindowManager backgroundWindowManager = + new BackgroundWindowManager(context, rootDisplayAreaOrganizer); OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context, settingsUtil, windowManager, backgroundWindowManager); OneHandedAnimationController animationController = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 000624499f79..f70736304f31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -23,6 +23,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.RemoteAction; @@ -73,6 +74,11 @@ public interface PipMenuController { void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction); /** + * Wait until the next frame to run the given Runnable. + */ + void runWithNextFrame(@NonNull Runnable runnable); + + /** * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a * need to synchronize the movements on the same frame as PiP. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index f170e774739f..2d7c5ce6feb5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -179,8 +179,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // This is necessary in case there was a resize animation ongoing when exit PIP // started, in which case the first resize will be skipped to let the exit // operation handle the final resize out of PIP mode. See b/185306679. - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); + finishResizeDelayedIfNeeded(() -> { + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); + }); } } @@ -196,6 +198,34 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; + /** + * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. + * + * This is done to avoid a race condition between the last transaction applied in + * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a + * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it + * gets applied before the transaction created by the last onAnimationUpdate. As a result of + * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which + * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize + * operation until the next frame. This aligns the last onAnimationUpdate transaction with the + * WCT application. + * + * The race only happens when the PiP surface transaction has to be synced with the PiP menu + * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and + * the PiP menu contents. + */ + private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) { + if (!shouldSyncPipTransactionWithMenu()) { + finishResizeRunnable.run(); + return; + } + mPipMenuController.runWithNextFrame(finishResizeRunnable); + } + + private boolean shouldSyncPipTransactionWithMenu() { + return mPipMenuController.isMenuVisible(); + } + @VisibleForTesting final PipTransitionController.PipTransitionCallback mPipTransitionCallback = new PipTransitionController.PipTransitionCallback() { @@ -221,7 +251,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, @Override public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds) { - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(leash, tx, destinationBounds); return true; } @@ -1223,7 +1253,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .crop(tx, mLeash, toBounds) .round(tx, mLeash, mPipTransitionState.isInPip()); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.resizePipMenu(mLeash, tx, toBounds); } else { tx.apply(); @@ -1265,7 +1295,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); - if (mPipMenuController.isMenuVisible()) { + if (shouldSyncPipTransactionWithMenu()) { mPipMenuController.movePipMenu(mLeash, tx, toBounds); } else { tx.apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 2b36b4c0307d..85bad174194c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -335,6 +335,7 @@ public class PipTransition extends PipTransitionController { final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), new Rect(mExitDestinationBounds), Surface.ROTATION_0); } mExitDestinationBounds.setEmpty(); @@ -475,6 +476,20 @@ public class PipTransition extends PipTransitionController { taskInfo); return; } + + // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which + // case it may not be in the screen coordinate. + // Reparent the pip leash to the root with max layer so that we can animate it outside of + // parent crop, and make sure it is not covered by other windows. + final SurfaceControl pipLeash = pipChange.getLeash(); + startTransaction.reparent(pipLeash, info.getRootLeash()); + startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); + // Note: because of this, the bounds to animate should be translated to the root coordinate. + final Point offset = info.getRootOffset(); + final Rect currentBounds = mPipBoundsState.getBounds(); + currentBounds.offset(-offset.x, -offset.y); + startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top); + mFinishCallback = (wct, wctCB) -> { mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(wct, wctCB); @@ -496,18 +511,17 @@ public class PipTransition extends PipTransitionController { if (displayRotationChange != null) { // Exiting PIP to fullscreen with orientation change. startExpandAndRotationAnimation(info, startTransaction, finishTransaction, - displayRotationChange, taskInfo, pipChange); + displayRotationChange, taskInfo, pipChange, offset); return; } } // Set the initial frame as scaling the end to the start. final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); - final Point offset = pipChange.getEndRelOffset(); destinationBounds.offset(-offset.x, -offset.y); - startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds); - mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(), - destinationBounds, mPipBoundsState.getBounds()); + startTransaction.setWindowCrop(pipLeash, destinationBounds); + mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds, + currentBounds); startTransaction.apply(); // Check if it is fixed rotation. @@ -532,19 +546,21 @@ public class PipTransition extends PipTransitionController { y = destinationBounds.bottom; } mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction, - pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y, + pipLeash, endBounds, endBounds, new Rect(), degree, x, y, true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */); } else { rotationDelta = Surface.ROTATION_0; } - startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta); + startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds, + rotationDelta); } private void startExpandAndRotationAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionInfo.Change displayRotationChange, - @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) { + @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange, + @NonNull Point offset) { final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(), displayRotationChange.getEndRotation()); @@ -556,7 +572,6 @@ public class PipTransition extends PipTransitionController { final Rect startBounds = new Rect(pipChange.getStartAbsBounds()); rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta); final Rect endBounds = new Rect(pipChange.getEndAbsBounds()); - final Point offset = pipChange.getEndRelOffset(); startBounds.offset(-offset.x, -offset.y); endBounds.offset(-offset.x, -offset.y); @@ -592,11 +607,12 @@ public class PipTransition extends PipTransitionController { } private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, - final Rect destinationBounds, final int rotationDelta) { + final Rect baseBounds, final Rect startBounds, final Rect endBounds, + final int rotationDelta) { final PipAnimationController.PipTransitionAnimator animator = - mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), - mPipBoundsState.getBounds(), destinationBounds, null, - TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta); + mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds, + endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP, + 0 /* startingAngle */, rotationDelta); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index a0a8f9fb2cde..531b9de1f913 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -305,6 +305,18 @@ public class PhonePipMenuController implements PipMenuController { showResizeHandle); } + @Override + public void runWithNextFrame(Runnable runnable) { + if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { + runnable.run(); + } + + mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> { + mMainHandler.post(runnable); + }); + mPipMenuView.invalidate(); + } + /** * Move the PiP menu, which does a translation and possibly a scale transformation. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 616d447247de..d28a9f3cf8ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -612,9 +612,24 @@ public class PipController implements PipTransitionController.PipTransitionCallb new DisplayInsetsController.OnInsetsChangedListener() { @Override public void insetsChanged(InsetsState insetsState) { + int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; onDisplayChanged( mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()), false /* saveRestoreSnapFraction */); + int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom; + if (!mEnablePipKeepClearAlgorithm) { + int pipTop = mPipBoundsState.getBounds().top; + int diff = newMaxMovementBound - oldMaxMovementBound; + if (diff < 0 && pipTop > newMaxMovementBound) { + // bottom inset has increased, move PiP up if it is too low + mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), + newMaxMovementBound - pipTop); + } + if (diff > 0 && oldMaxMovementBound == pipTop) { + // bottom inset has decreased, move PiP down if it was by the edge + mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff); + } + } } }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 975d4bba276e..a9a97beb9180 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -427,7 +427,7 @@ public class PipTouchHandler { // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not // occluded by the IME or shelf. if (fromImeAdjustment || fromShelfAdjustment) { - if (mTouchState.isUserInteracting()) { + if (mTouchState.isUserInteracting() && mTouchState.isDragging()) { // Defer the update of the current movement bounds until after the user finishes // touching the screen } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index ab7edbfaa4ca..c39400ab61e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -421,6 +421,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } @Override + public void runWithNextFrame(Runnable runnable) { + if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) { + runnable.run(); + } + + mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> { + mMainHandler.post(runnable); + }); + mPipMenuView.invalidate(); + } + + @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction, Rect pipDestBounds) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 943419bb8ea2..15a11334307b 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 @@ -115,6 +115,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ScreenshotUtils; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.TransactionPool; @@ -846,15 +847,44 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { if (mSideStagePosition == sideStagePosition) return; SurfaceControl.Transaction t = mTransactionPool.acquire(); + mTempRect1.setEmpty(); final StageTaskListener topLeftStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage; + final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t, + topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1); final StageTaskListener bottomRightStage = mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage; + final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t, + bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1); mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash, - () -> { - setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), - null /* wct */); - mTransactionPool.release(t); + insets -> { + WindowContainerTransaction wct = new WindowContainerTransaction(); + setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(st -> { + updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */); + st.setPosition(topLeftScreenshot, -insets.left, -insets.top); + st.setPosition(bottomRightScreenshot, insets.left, insets.top); + + final ValueAnimator va = ValueAnimator.ofFloat(1, 0); + va.addUpdateListener(valueAnimator-> { + final float progress = (float) valueAnimator.getAnimatedValue(); + t.setAlpha(topLeftScreenshot, progress); + t.setAlpha(bottomRightScreenshot, progress); + t.apply(); + }); + va.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd( + @androidx.annotation.NonNull Animator animation) { + t.remove(topLeftScreenshot); + t.remove(bottomRightScreenshot); + t.apply(); + mTransactionPool.release(t); + } + }); + va.start(); + }); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index af79386caf9c..928e71f8d3a6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -46,6 +46,7 @@ import static android.window.TransitionInfo.FLAG_CROSS_PROFILE_WORK_THUMBNAIL; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; @@ -319,6 +320,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { final int wallpaperTransit = getWallpaperTransitType(info); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); + if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY + | FLAG_IS_BEHIND_STARTING_WINDOW)) { + // Don't animate embedded activity if it is covered by the starting window. + // Non-embedded case still needs animation because the container can still animate + // the starting window together, e.g. CLOSE or CHANGE type. + continue; + } + if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) { + // Wallpaper, IME, and system windows don't need any default animations. + continue; + } final boolean isTask = change.getTaskInfo() != null; boolean isSeamlessDisplayChange = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 63d31cde4715..89205a6dfb01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -341,6 +341,15 @@ public class Transitions implements RemoteCallable<Transitions> { final SurfaceControl leash = change.getLeash(); final int mode = info.getChanges().get(i).getMode(); + if (mode == TRANSIT_TO_FRONT + && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height() + || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) { + // When the window is moved to front with a different size, make sure the crop is + // updated to prevent it from using the old crop. + t.setWindowCrop(leash, change.getEndAbsBounds().width(), + change.getEndAbsBounds().height()); + } + // Don't move anything that isn't independent within its parents if (!TransitionInfo.isIndependent(change, info)) { if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) { 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 dca516a327b0..36dd8edaa8b7 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 @@ -26,11 +26,16 @@ import android.app.ActivityTaskManager; import android.content.Context; import android.hardware.input.InputManager; import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; +import android.view.InputChannel; import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.InputMonitor; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -64,8 +69,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final SyncTransactionQueue mSyncQueue; private FreeformTaskTransitionStarter mTransitionStarter; private DesktopModeController mDesktopModeController; + private EventReceiver mEventReceiver; + private InputMonitor mInputMonitor; private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); + private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); public CaptionWindowDecorViewModel( Context context, @@ -108,12 +116,19 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration); + TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration, + mDragStartListener); CaptionTouchEventListener touchEventListener = new CaptionTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragResizeCallback(taskPositioner); setupWindowDecorationForTransition(taskInfo, startT, finishT); + if (mInputMonitor == null) { + mInputMonitor = InputManager.getInstance().monitorGestureInput( + "caption-touch", mContext.getDisplayId()); + mEventReceiver = new EventReceiver( + mInputMonitor.getInputChannel(), Looper.myLooper()); + } return true; } @@ -165,6 +180,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { @Override public void onClick(View v) { + CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int id = v.getId(); if (id == R.id.close_window) { WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -176,6 +192,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } } else if (id == R.id.back_button) { injectBackKey(); + } else if (id == R.id.caption_handle) { + decoration.createHandleMenu(); + } else if (id == R.id.desktop_button) { + mDesktopModeController.setDesktopModeActive(true); + decoration.closeHandleMenu(); + } else if (id == R.id.fullscreen_button) { + mDesktopModeController.setDesktopModeActive(false); + decoration.closeHandleMenu(); + decoration.setButtonVisibility(); } } private void injectBackKey() { @@ -257,6 +282,36 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } } + // InputEventReceiver to listen for touch input outside of caption bounds + private class EventReceiver extends InputEventReceiver { + EventReceiver(InputChannel channel, Looper looper) { + super(channel, looper); + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = false; + if (event instanceof MotionEvent + && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) { + handled = true; + CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event); + } + finishInputEvent(event, handled); + } + } + + // If any input received is outside of caption bounds, turn off handle menu + private void handleMotionEvent(MotionEvent ev) { + int size = mWindowDecorByTaskId.size(); + for (int i = 0; i < size; i++) { + CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i); + if (decoration != null) { + decoration.closeHandleMenuIfNeeded(ev); + } + } + } + + private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; return DesktopModeStatus.IS_SUPPORTED @@ -264,4 +319,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { && mDisplayController.getDisplayContext(taskInfo.displayId) .getResources().getConfiguration().smallestScreenWidthDp >= 600; } + + private class DragStartListenerImpl implements TaskPositioner.DragStartListener{ + @Override + public void onDragStart(int taskId) { + mWindowDecorByTaskId.get(taskId).closeHandleMenu(); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 9d61c14e1435..03cad043ed67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -20,10 +20,14 @@ import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.view.Choreographer; +import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; @@ -58,6 +62,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private boolean mDesktopActive; + private AdditionalWindow mHandleMenu; + CaptionWindowDecoration( Context context, DisplayController displayController, @@ -123,7 +129,20 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL if (isDragResizeable) { mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); } + final Resources resources = mDecorWindowContext.getResources(); + final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final int captionHeight = loadDimensionPixelSize(resources, + mRelayoutParams.mCaptionHeightId); + final int captionWidth = loadDimensionPixelSize(resources, + mRelayoutParams.mCaptionWidthId); + final int captionLeft = taskBounds.width() / 2 + - captionWidth / 2; + final int captionTop = taskBounds.top + <= captionHeight / 2 ? 0 : -captionHeight / 2; + mRelayoutParams.setCaptionPosition(captionLeft, captionTop); + relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); + taskInfo = null; // Clear it just in case we use it accidentally mTaskOrganizer.applyTransaction(wct); @@ -137,15 +156,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL } // If this task is not focused, do not show caption. - setCaptionVisibility(taskInfo.isFocused); + setCaptionVisibility(mTaskInfo.isFocused); // Only handle should show if Desktop Mode is inactive. boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext); - if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) { + if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) { mDesktopActive = desktopCurrentStatus; setButtonVisibility(); } - taskInfo = null; // Clear it just in case we use it accidentally if (!isDragResizeable) { closeDragResizeListener(); @@ -184,9 +202,22 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL back.setOnClickListener(mOnCaptionButtonClickListener); View handle = caption.findViewById(R.id.caption_handle); handle.setOnTouchListener(mOnCaptionTouchListener); + handle.setOnClickListener(mOnCaptionButtonClickListener); setButtonVisibility(); } + private void setupHandleMenu() { + View menu = mHandleMenu.mWindowViewHost.getView(); + View fullscreen = menu.findViewById(R.id.fullscreen_button); + fullscreen.setOnClickListener(mOnCaptionButtonClickListener); + View desktop = menu.findViewById(R.id.desktop_button); + desktop.setOnClickListener(mOnCaptionButtonClickListener); + View split = menu.findViewById(R.id.split_screen_button); + split.setOnClickListener(mOnCaptionButtonClickListener); + View more = menu.findViewById(R.id.more_button); + more.setOnClickListener(mOnCaptionButtonClickListener); + } + /** * Sets caption visibility based on task focus. * @@ -194,8 +225,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL */ private void setCaptionVisibility(boolean visible) { int v = visible ? View.VISIBLE : View.GONE; - View caption = mResult.mRootView.findViewById(R.id.caption); - caption.setVisibility(v); + View captionView = mResult.mRootView.findViewById(R.id.caption); + captionView.setVisibility(v); + if (!visible) closeHandleMenu(); } /** @@ -203,6 +235,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL * */ public void setButtonVisibility() { + mDesktopActive = DesktopModeStatus.isActive(mContext); int v = mDesktopActive ? View.VISIBLE : View.GONE; View caption = mResult.mRootView.findViewById(R.id.caption); View back = caption.findViewById(R.id.back_button); @@ -220,6 +253,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT); } + public boolean isHandleMenuActive() { + return mHandleMenu != null; + } + private void closeDragResizeListener() { if (mDragResizeListener == null) { return; @@ -228,9 +265,67 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mDragResizeListener = null; } + /** + * Create and display handle menu window + */ + public void createHandleMenu() { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + final Resources resources = mDecorWindowContext.getResources(); + int x = mRelayoutParams.mCaptionX; + int y = mRelayoutParams.mCaptionY; + int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); + int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); + String namePrefix = "Caption Menu"; + mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t, + x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY, + width, height); + mSyncQueue.runInSync(transaction -> { + transaction.merge(t); + t.close(); + }); + setupHandleMenu(); + } + + /** + * Close the handle menu window + */ + public void closeHandleMenu() { + if (!isHandleMenuActive()) return; + mHandleMenu.releaseView(); + mHandleMenu = null; + } + + @Override + void releaseViews() { + closeHandleMenu(); + super.releaseViews(); + } + + /** + * Close an open handle menu if input is outside of menu coordinates + * @param ev the tapped point to compare against + * @return + */ + public void closeHandleMenuIfNeeded(MotionEvent ev) { + if (mHandleMenu != null) { + Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId) + .positionInParent; + final Resources resources = mDecorWindowContext.getResources(); + ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY); + ev.offsetLocation(-positionInParent.x, -positionInParent.y); + int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); + int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); + if (!(ev.getX() >= 0 && ev.getY() >= 0 + && ev.getX() <= width && ev.getY() <= height)) { + closeHandleMenu(); + } + } + } + @Override public void close() { closeDragResizeListener(); + closeHandleMenu(); super.close(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java index 27c10114ac0e..f0f2db7ded80 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -42,14 +42,18 @@ class TaskPositioner implements DragResizeCallback { private final Rect mResizeTaskBounds = new Rect(); private int mCtrlType; + private DragStartListener mDragStartListener; - TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) { + TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, + DragStartListener dragStartListener) { mTaskOrganizer = taskOrganizer; mWindowDecoration = windowDecoration; + mDragStartListener = dragStartListener; } @Override public void onDragResizeStart(int ctrlType, float x, float y) { + mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId); mCtrlType = ctrlType; mTaskBoundsAtDragStart.set( @@ -97,4 +101,12 @@ class TaskPositioner implements DragResizeCallback { mTaskOrganizer.applyTransaction(wct); } } + + interface DragStartListener { + /** + * Inform the implementing class that a drag resize has started + * @param taskId id of this positioner's {@link WindowDecoration} + */ + void onDragStart(int taskId); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index b314163802ca..7ecb3f3f6355 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -200,16 +200,17 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); final Resources resources = mDecorWindowContext.getResources(); - final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); - final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); + outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); + outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); outResult.mWidth = taskBounds.width() + loadDimensionPixelSize(resources, params.mOutsetRightId) - - decorContainerOffsetX; + - outResult.mDecorContainerOffsetX; outResult.mHeight = taskBounds.height() + loadDimensionPixelSize(resources, params.mOutsetBottomId) - - decorContainerOffsetY; + - outResult.mDecorContainerOffsetY; startT.setPosition( - mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY) + mDecorationContainerSurface, + outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY) .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) // TODO(b/244455401): Change the z-order when it's better organized @@ -252,14 +253,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId); - //Prevent caption from going offscreen if task is too high up - final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2; - startT.setPosition( - mCaptionContainerSurface, -decorContainerOffsetX - + taskBounds.width() / 2 - captionWidth / 2, - -decorContainerOffsetY - captionYPos) - .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight) + mCaptionContainerSurface, + -outResult.mDecorContainerOffsetX + params.mCaptionX, + -outResult.mDecorContainerOffsetY + params.mCaptionY) + .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) .show(mCaptionContainerSurface); if (mCaptionWindowManager == null) { @@ -292,7 +290,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Caption insets mCaptionInsetsRect.set(taskBounds); mCaptionInsetsRect.bottom = - mCaptionInsetsRect.top + captionHeight - captionYPos; + mCaptionInsetsRect.top + captionHeight + params.mCaptionY; wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES); } else { @@ -302,10 +300,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Task surface itself Point taskPosition = mTaskInfo.positionInParent; mTaskSurfaceCrop.set( - decorContainerOffsetX, - decorContainerOffsetY, - outResult.mWidth + decorContainerOffsetX, - outResult.mHeight + decorContainerOffsetY); + outResult.mDecorContainerOffsetX, + outResult.mDecorContainerOffsetY, + outResult.mWidth + outResult.mDecorContainerOffsetX, + outResult.mHeight + outResult.mDecorContainerOffsetY); startT.show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) .setCrop(mTaskSurface, mTaskSurfaceCrop); @@ -326,7 +324,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return true; } - private void releaseViews() { + void releaseViews() { if (mViewHost != null) { mViewHost.release(); mViewHost = null; @@ -369,20 +367,60 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> releaseViews(); } - private static int loadDimensionPixelSize(Resources resources, int resourceId) { + static int loadDimensionPixelSize(Resources resources, int resourceId) { if (resourceId == Resources.ID_NULL) { return 0; } return resources.getDimensionPixelSize(resourceId); } - private static float loadDimension(Resources resources, int resourceId) { + static float loadDimension(Resources resources, int resourceId) { if (resourceId == Resources.ID_NULL) { return 0; } return resources.getDimension(resourceId); } + /** + * Create a window associated with this WindowDecoration. + * Note that subclass must dispose of this when the task is hidden/closed. + * @param layoutId layout to make the window from + * @param t the transaction to apply + * @param xPos x position of new window + * @param yPos y position of new window + * @param width width of new window + * @param height height of new window + * @return + */ + AdditionalWindow addWindow(int layoutId, String namePrefix, + SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) { + final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); + SurfaceControl windowSurfaceControl = builder + .setName(namePrefix + " of Task=" + mTaskInfo.taskId) + .setContainerLayer() + .setParent(mDecorationContainerSurface) + .build(); + View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null); + + t.setPosition( + windowSurfaceControl, xPos, yPos) + .setWindowCrop(windowSurfaceControl, width, height) + .show(windowSurfaceControl); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT); + lp.setTitle("Additional window of Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration, + windowSurfaceControl, null /* hostInputToken */); + SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory + .create(mDecorWindowContext, mDisplay, windowManager); + viewHost.setView(v, lp); + return new AdditionalWindow(windowSurfaceControl, viewHost, + mSurfaceControlTransactionSupplier); + } + static class RelayoutParams{ RunningTaskInfo mRunningTaskInfo; int mLayoutResId; @@ -395,6 +433,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mOutsetLeftId; int mOutsetRightId; + int mCaptionX; + int mCaptionY; + void setOutsets(int leftId, int topId, int rightId, int bottomId) { mOutsetLeftId = leftId; mOutsetTopId = topId; @@ -402,6 +443,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mOutsetBottomId = bottomId; } + void setCaptionPosition(int left, int top) { + mCaptionX = left; + mCaptionY = top; + } + void reset() { mLayoutResId = Resources.ID_NULL; mCaptionHeightId = Resources.ID_NULL; @@ -412,6 +458,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mOutsetBottomId = Resources.ID_NULL; mOutsetLeftId = Resources.ID_NULL; mOutsetRightId = Resources.ID_NULL; + + mCaptionX = 0; + mCaptionY = 0; } } @@ -419,10 +468,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mWidth; int mHeight; T mRootView; + int mDecorContainerOffsetX; + int mDecorContainerOffsetY; void reset() { mWidth = 0; mHeight = 0; + mDecorContainerOffsetX = 0; + mDecorContainerOffsetY = 0; mRootView = null; } } @@ -432,4 +485,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return new SurfaceControlViewHost(c, d, wmm); } } + + /** + * Subclass for additional windows associated with this WindowDecoration + */ + static class AdditionalWindow { + SurfaceControl mWindowSurface; + SurfaceControlViewHost mWindowViewHost; + Supplier<SurfaceControl.Transaction> mTransactionSupplier; + + private AdditionalWindow(SurfaceControl surfaceControl, + SurfaceControlViewHost surfaceControlViewHost, + Supplier<SurfaceControl.Transaction> transactionSupplier) { + mWindowSurface = surfaceControl; + mWindowViewHost = surfaceControlViewHost; + mTransactionSupplier = transactionSupplier; + } + + void releaseView() { + WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM(); + + if (mWindowViewHost != null) { + mWindowViewHost.release(); + mWindowViewHost = null; + } + windowManager = null; + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + boolean released = false; + if (mWindowSurface != null) { + t.remove(mWindowSurface); + mWindowSurface = null; + released = true; + } + if (released) { + t.apply(); + } + } + } } diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index f4efc374ecc2..1c28c3d58ccb 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -6,3 +6,4 @@ pablogamito@google.com lbill@google.com madym@google.com hwwang@google.com +chenghsiuchang@google.com diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt index 79978929cf3e..651d9356d9ba 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt @@ -21,6 +21,7 @@ package com.android.wm.shell.flicker import com.android.server.wm.traces.common.ComponentNameMatcher const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" +const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher" val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#") val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#") val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#") 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 f8025396ddc9..7546a55c08fa 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 @@ -80,7 +80,7 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest( transitions { tapl.goHome() } } - @FlakyTest + @FlakyTest(bugId = 256863309) @Test override fun pipLayerReduces() { testSpec.assertLayers { @@ -108,14 +108,6 @@ class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest( } } - @FlakyTest(bugId = 239807171) - @Test - override fun pipAppLayerAlwaysVisible() = super.pipAppLayerAlwaysVisible() - - @FlakyTest(bugId = 239807171) - @Test - override fun pipLayerRemainInsideVisibleBounds() = super.pipLayerRemainInsideVisibleBounds() - @Presubmit @Test override fun focusChanges() { 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 9e765752ea9e..2bce8e45c553 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 @@ -53,7 +53,7 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS override val transition: FlickerBuilder.() -> Unit get() = { super.transition(this) - setup { SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp) } + setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) } transitions { SplitScreenUtils.copyContentInSplit( instrumentation, 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 45eae2e2fe40..475749834711 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 @@ -55,7 +55,7 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } transitions { if (tapl.isTablet) { 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 6cfbb4789dc1..1d61955bc0a8 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 @@ -52,7 +52,7 @@ class DismissSplitScreenByGoHome( get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } transitions { tapl.goHome() 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 a80c88aad3d9..8d771fe3a1ff 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 @@ -56,7 +56,7 @@ class DragDividerToResize(testSpec: FlickerTestParameter) : SplitScreenBase(test get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) 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 new file mode 100644 index 000000000000..1a29193e7517 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.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.wm.shell.flicker.splitscreen + +import android.view.WindowManagerPolicyConstants +import android.platform.test.annotations.Postsubmit +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.wm.shell.flicker.appWindowBecomesVisible +import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd +import com.android.wm.shell.flicker.layerBecomesVisible +import com.android.wm.shell.flicker.layerIsVisibleAtEnd +import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag +import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd +import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible +import com.android.wm.shell.flicker.splitScreenEntered +import org.junit.Assume +import org.junit.Before +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test enter split screen by dragging a shortcut. + * This test is only for large screen devices. + * + * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class EnterSplitScreenByDragFromShortcut( + testSpec: FlickerTestParameter +) : SplitScreenBase(testSpec) { + + @Before + fun before() { + Assume.assumeTrue(testSpec.isTablet) + } + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + transitions { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .openDeepShortcutMenu() + .getMenuItem("Split Screen Secondary Activity") + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + } + + @Postsubmit + @Test + fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false) + + @Postsubmit + @Test + fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible() + + @Postsubmit + @Test + fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) + + @Postsubmit + @Test + fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp) + + @Postsubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, landscapePosLeft = false, portraitPosTop = false) + + @Postsubmit + @Test + fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag( + secondaryApp) + + @Postsubmit + @Test + fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp) + + @Postsubmit + @Test + fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp) + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun entireScreenCovered() = + super.entireScreenCovered() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = + super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarLayerPositionAtStartAndEnd() = + super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun navBarWindowIsAlwaysVisible() = + super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = + super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = + super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = + super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = + super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + 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)) + } + } +}
\ No newline at end of file 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 936afa9801b7..fb7b8b7926e3 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 @@ -34,7 +34,6 @@ import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible import com.android.wm.shell.flicker.splitScreenEntered import org.junit.Assume -import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -55,7 +54,6 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen get() = { super.transition(this) setup { - tapl.workspace.switchToOverview().dismissAllTasks() primaryApp.launchViaIntent(wmHelper) secondaryApp.launchViaIntent(wmHelper) tapl.goHome() @@ -65,7 +63,7 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen .waitForAndVerify() } transitions { - SplitScreenUtils.splitFromOverview(tapl) + SplitScreenUtils.splitFromOverview(tapl, device) SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } } 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 e6d6379e750c..c8413337a1e6 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 @@ -34,6 +34,7 @@ abstract class SplitScreenBase(testSpec: FlickerTestParameter) : BaseTest(testSp tapl.setEnableRotation(true) setRotation(testSpec.startRotation) tapl.setExpectedRotation(testSpec.startRotation) + tapl.workspace.switchToOverview().dismissAllTasks() } teardown { primaryApp.exit(wmHelper) 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 6453ed869681..ead451f07653 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 @@ -25,6 +25,7 @@ import android.view.ViewConfiguration import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 import androidx.test.uiautomator.Until import com.android.launcher3.tapl.LauncherInstrumentation import com.android.server.wm.flicker.helpers.ImeAppHelper @@ -38,13 +39,16 @@ import com.android.server.wm.traces.common.IComponentMatcher import com.android.server.wm.traces.common.IComponentNameMatcher import com.android.server.wm.traces.parser.toFlickerComponent 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 java.util.Collections internal object SplitScreenUtils { private const val TIMEOUT_MS = 3_000L private const val DRAG_DURATION_MS = 1_000L private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" private const val DIVIDER_BAR = "docked_divider_handle" + private const val OVERVIEW_SNAPSHOT = "snapshot" private const val GESTURE_STEP_MS = 16L private const val LONG_PRESS_TIME_MS = 100L private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") @@ -55,6 +59,8 @@ internal object SplitScreenUtils { get() = By.text("Flicker Test Notification") private val dividerBarSelector: BySelector get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + private val overviewSnapshotSelector: BySelector + get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = SimpleAppHelper( @@ -94,24 +100,39 @@ internal object SplitScreenUtils { fun enterSplit( wmHelper: WindowManagerStateHelper, tapl: LauncherInstrumentation, + device: UiDevice, primaryApp: StandardAppHelper, secondaryApp: StandardAppHelper ) { - tapl.workspace.switchToOverview().dismissAllTasks() primaryApp.launchViaIntent(wmHelper) secondaryApp.launchViaIntent(wmHelper) tapl.goHome() wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() - splitFromOverview(tapl) + splitFromOverview(tapl, device) waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } - fun splitFromOverview(tapl: LauncherInstrumentation) { + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { // Note: The initial split position in landscape is different between tablet and phone. // In landscape, tablet will let the first app split to right side, and phone will // split to left side. if (tapl.isTablet) { - tapl.workspace.switchToOverview().overviewActions.clickSplit().currentTask.open() + // TAPL's currentTask on tablet is sometimes not what we expected if the overview + // contains more than 3 task views. We need to use uiautomator directly to find the + // second task to split. + tapl.workspace.switchToOverview().overviewActions.clickSplit() + val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) + if (snapshots == null || snapshots.size < 1) { + error("Fail to find a overview snapshot to split.") + } + + // 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}) + snapshots[0].click() } else { tapl.workspace .switchToOverview() 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 73159c981b82..f7610c48a0f8 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 @@ -56,7 +56,7 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) } transitions { SplitScreenUtils.doubleTapDividerToSwitch(device) @@ -145,15 +145,19 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB // robust enough to get the correct end state. } + @Presubmit @Test fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + @Presubmit @Test fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp) + @Presubmit @Test fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp) + @Presubmit @Test fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( primaryApp, @@ -161,6 +165,7 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB portraitPosTop = true ) + @Presubmit @Test fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd( secondaryApp, @@ -168,9 +173,11 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB portraitPosTop = false ) + @Presubmit @Test fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp) + @Presubmit @Test fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp) 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 553840cf0e47..993dba28bbc4 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 @@ -52,7 +52,7 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) thirdApp.launchViaIntent(wmHelper) wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify() 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 1f117d0cbd94..2a552cdd67e8 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 @@ -51,7 +51,7 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) tapl.goHome() wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() 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 d7b3ec2256c1..7f81baef315b 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 @@ -51,7 +51,7 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB get() = { super.transition(this) setup { - SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) tapl.goHome() wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() 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 new file mode 100644 index 000000000000..d84954dcdd09 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.splitscreen + +import android.platform.test.annotations.FlakyTest +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.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.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 org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switch between two split pairs. + * + * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) { + private val thirdApp = SplitScreenUtils.getIme(instrumentation) + private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) + + override val transition: FlickerBuilder.() -> Unit + get() = { + super.transition(this) + setup { + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp) + } + transitions { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + teardown { + thirdApp.exit(wmHelper) + fourthApp.exit(wmHelper) + } + } + + @Postsubmit + @Test + fun cujCompleted() { + testSpec.appWindowIsVisibleAtStart(thirdApp) + testSpec.appWindowIsVisibleAtStart(fourthApp) + testSpec.splitScreenDividerIsVisibleAtStart() + + testSpec.appWindowIsVisibleAtEnd(primaryApp) + testSpec.appWindowIsVisibleAtEnd(secondaryApp) + testSpec.appWindowIsInvisibleAtEnd(thirdApp) + testSpec.appWindowIsInvisibleAtEnd(fourthApp) + testSpec.splitScreenDividerIsVisibleAtEnd() + } + + @Postsubmit + @Test + fun splitScreenDividerInvisibleAtMiddle() = + testSpec.assertLayers { + this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + .then() + .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT) + } + + @FlakyTest(bugId = 247095572) + @Test + fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp) + + @FlakyTest(bugId = 247095572) + @Test + fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp) + + @FlakyTest(bugId = 247095572) + @Test + fun thirdAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(thirdApp) + + @FlakyTest(bugId = 247095572) + @Test + fun fourthAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(fourthApp) + + @Postsubmit + @Test + fun primaryAppBoundsIsVisibleAtEnd() = + testSpec.splitAppLayerBoundsIsVisibleAtEnd( + primaryApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false + ) + + @Postsubmit + @Test + fun secondaryAppBoundsIsVisibleAtEnd() = + testSpec.splitAppLayerBoundsIsVisibleAtEnd( + secondaryApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true + ) + + @Postsubmit + @Test + fun thirdAppBoundsIsVisibleAtBegin() = + testSpec.assertLayersStart { + this.splitAppLayerBoundsSnapToDivider( + thirdApp, + landscapePosLeft = tapl.isTablet, + portraitPosTop = false, + testSpec.startRotation + ) + } + + @Postsubmit + @Test + fun fourthAppBoundsIsVisibleAtBegin() = + testSpec.assertLayersStart { + this.splitAppLayerBoundsSnapToDivider( + fourthApp, + landscapePosLeft = !tapl.isTablet, + portraitPosTop = true, + testSpec.startRotation + ) + } + + @Postsubmit + @Test + fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp) + + @Postsubmit + @Test + fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp) + + @Postsubmit + @Test + fun thirdAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(thirdApp) + + @Postsubmit + @Test + fun fourthAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(fourthApp) + + /** {@inheritDoc} */ + @FlakyTest(bugId = 251268711) + @Test + override fun entireScreenCovered() = + super.entireScreenCovered() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = + super.navBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @FlakyTest(bugId = 206753786) + @Test + override fun navBarLayerPositionAtStartAndEnd() = + super.navBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun navBarWindowIsAlwaysVisible() = + super.navBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerIsVisibleAtStartAndEnd() = + super.statusBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarLayerPositionAtStartAndEnd() = + super.statusBarLayerPositionAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun statusBarWindowIsAlwaysVisible() = + super.statusBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = + super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun taskBarWindowIsAlwaysVisible() = + super.taskBarWindowIsAlwaysVisible() + + /** {@inheritDoc} */ + @FlakyTest + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() = + super.visibleLayersShownMoreThanOneConsecutiveEntry() + + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests() + } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java index f3f70673b332..11948dbf9659 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java @@ -24,6 +24,7 @@ import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.wm.shell.RootDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; @@ -41,11 +42,13 @@ public class BackgroundWindowManagerTest extends ShellTestCase { private BackgroundWindowManager mBackgroundWindowManager; @Mock private DisplayLayout mMockDisplayLayout; + @Mock + private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer; @Before public void setup() { MockitoAnnotations.initMocks(this); - mBackgroundWindowManager = new BackgroundWindowManager(mContext); + mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer); mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 55883ab2ef70..d01f3d310fc3 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 @@ -39,6 +39,7 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -110,6 +111,7 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + @UiThreadTest public void instantiateController_registerDumpCallback() { doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); @@ -118,6 +120,7 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + @UiThreadTest public void instantiateController_registerCommandCallback() { doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); @@ -126,6 +129,7 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + @UiThreadTest public void testControllerRegistersKeyguardChangeListener() { doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); @@ -134,6 +138,7 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + @UiThreadTest public void instantiateController_addExternalInterface() { doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor(); when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 835087007b30..3569860b6128 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -50,6 +50,7 @@ import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -113,6 +114,7 @@ public class StageCoordinatorTests extends ShellTestCase { private StageCoordinator mStageCoordinator; @Before + @UiThreadTest public void setup() { MockitoAnnotations.initMocks(this); mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 4d37e5dbc4dc..15181b1549f5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.content.Context; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -64,6 +65,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import java.util.ArrayList; import java.util.List; @@ -102,12 +104,14 @@ public class WindowDecorationTests extends ShellTestCase { private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>(); private SurfaceControl.Transaction mMockSurfaceControlStartT; private SurfaceControl.Transaction mMockSurfaceControlFinishT; + private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams(); @Before public void setUp() { mMockSurfaceControlStartT = createMockSurfaceControlTransaction(); mMockSurfaceControlFinishT = createMockSurfaceControlTransaction(); + mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction(); doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory) .create(any(), any(), any()); @@ -227,8 +231,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8); - verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); + verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); + verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); @@ -242,7 +246,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockView).setTaskFocusState(true); verify(mMockWindowContainerTransaction) .addRectInsetsProvider(taskInfo.token, - new Rect(100, 300, 400, 332), + new Rect(100, 300, 400, 364), new int[] { InsetsState.ITYPE_CAPTION_BAR }); } @@ -366,6 +370,71 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlViewHost).setView(same(mMockView), any()); } + @Test + public void testAddWindow() { + final Display defaultDisplay = mock(Display.class); + doReturn(defaultDisplay).when(mMockDisplayController) + .getDisplay(Display.DEFAULT_DISPLAY); + + final SurfaceControl decorContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder decorContainerSurfaceBuilder = + createMockSurfaceControlBuilder(decorContainerSurface); + mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); + final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder taskBackgroundSurfaceBuilder = + createMockSurfaceControlBuilder(taskBackgroundSurface); + mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); + final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder captionContainerSurfaceBuilder = + createMockSurfaceControlBuilder(captionContainerSurface); + mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); + + final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); + mMockSurfaceControlTransactions.add(t); + final ActivityManager.TaskDescription.Builder taskDescriptionBuilder = + new ActivityManager.TaskDescription.Builder() + .setBackgroundColor(Color.YELLOW); + final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setDisplayId(Display.DEFAULT_DISPLAY) + .setTaskDescriptionBuilder(taskDescriptionBuilder) + .setBounds(TASK_BOUNDS) + .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y) + .setVisible(true) + .build(); + taskInfo.isFocused = true; + taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; + mRelayoutParams.setOutsets( + R.dimen.test_window_decor_left_outset, + R.dimen.test_window_decor_top_outset, + R.dimen.test_window_decor_right_outset, + R.dimen.test_window_decor_bottom_outset); + final SurfaceControl taskSurface = mock(SurfaceControl.class); + final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); + windowDecor.relayout(taskInfo); + + final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class); + final SurfaceControl.Builder additionalWindowSurfaceBuilder = + createMockSurfaceControlBuilder(additionalWindowSurface); + mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder); + + WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow(); + + verify(additionalWindowSurfaceBuilder).setContainerLayer(); + verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface); + verify(additionalWindowSurfaceBuilder).build(); + verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40); + verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64); + verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface); + verify(mMockSurfaceControlViewHostFactory, Mockito.times(2)) + .create(any(), eq(defaultDisplay), any()); + assertThat(additionalWindow.mWindowViewHost).isNotNull(); + + additionalWindow.releaseView(); + + assertThat(additionalWindow.mWindowViewHost).isNull(); + assertThat(additionalWindow.mWindowSurface).isNull(); + } + private TestWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) { return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(), @@ -429,5 +498,20 @@ public class WindowDecorationTests extends ShellTestCase { relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, mMockWindowContainerTransaction, mMockView, mRelayoutResult); } + + private WindowDecoration.AdditionalWindow addTestWindow() { + final Resources resources = mDecorWindowContext.getResources(); + int x = mRelayoutParams.mCaptionX; + int y = mRelayoutParams.mCaptionY; + int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId); + int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId); + String name = "Test Window"; + WindowDecoration.AdditionalWindow additionalWindow = + addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT, + x - mRelayoutResult.mDecorContainerOffsetX, + y - mRelayoutResult.mDecorContainerOffsetY, + width, height); + return additionalWindow; + } } } diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp index 15be80c48192..d1cfd03276c2 100644 --- a/libs/androidfw/tests/CursorWindow_test.cpp +++ b/libs/androidfw/tests/CursorWindow_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <memory> #include <utility> #include "androidfw/CursorWindow.h" @@ -184,7 +185,7 @@ TEST(CursorWindowTest, Inflate) { ASSERT_EQ(w->allocRow(), OK); // Scratch buffer that will fit before inflation - void* buf = malloc(kHalfInlineSize); + char buf[kHalfInlineSize]; // Store simple value ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); @@ -262,7 +263,7 @@ TEST(CursorWindowTest, ParcelSmall) { ASSERT_EQ(w->allocRow(), OK); // Scratch buffer that will fit before inflation - void* buf = malloc(kHalfInlineSize); + char buf[kHalfInlineSize]; // Store simple value ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); @@ -322,7 +323,8 @@ TEST(CursorWindowTest, ParcelLarge) { ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); // Store object that forces inflation - void* buf = malloc(kGiantSize); + std::unique_ptr<char> bufPtr(new char[kGiantSize]); + void* buf = bufPtr.get(); memset(buf, 42, kGiantSize); ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 29f37737d433..cccc0f81117b 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -678,6 +678,7 @@ cc_test { srcs: [ "tests/unit/main.cpp", "tests/unit/ABitmapTests.cpp", + "tests/unit/AutoBackendTextureReleaseTests.cpp", "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", "tests/unit/CanvasOpTests.cpp", diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp index ef5eacbdb4ad..b656b6ac8204 100644 --- a/libs/hwui/AutoBackendTextureRelease.cpp +++ b/libs/hwui/AutoBackendTextureRelease.cpp @@ -32,9 +32,17 @@ AutoBackendTextureRelease::AutoBackendTextureRelease(GrDirectContext* context, bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT); GrBackendFormat backendFormat = GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false); + LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(), + __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32 + ", AHardwareBuffer_Format==%" PRIu32 ".", + static_cast<int>(context->backend()), desc.format); mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture( context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx, createProtectedImage, backendFormat, false); + LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(), + __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32 + ", protected==%d", + desc.width, desc.height, createProtectedImage); } void AutoBackendTextureRelease::unref(bool releaseImage) { @@ -74,13 +82,13 @@ void AutoBackendTextureRelease::makeImage(AHardwareBuffer* buffer, AHardwareBuffer_Desc desc; AHardwareBuffer_describe(buffer, &desc); SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format); + // The following ref will be counteracted by Skia calling releaseProc, either during + // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must + // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure. + ref(); mImage = SkImage::MakeFromTexture( context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType, uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this); - if (mImage.get()) { - // The following ref will be counteracted by releaseProc, when SkImage is discarded. - ref(); - } } void AutoBackendTextureRelease::newBufferContent(GrDirectContext* context) { diff --git a/libs/hwui/AutoBackendTextureRelease.h b/libs/hwui/AutoBackendTextureRelease.h index c9bb767a3185..f0eb2a8b6eab 100644 --- a/libs/hwui/AutoBackendTextureRelease.h +++ b/libs/hwui/AutoBackendTextureRelease.h @@ -25,6 +25,9 @@ namespace android { namespace uirenderer { +// Friend TestUtils serves as a proxy for any test cases that require access to private members. +class TestUtils; + /** * AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object * that keeps GPU resources alive until the last SkImage object using them is destroyed. @@ -66,6 +69,9 @@ private: // mImage is the SkImage created from mBackendTexture. sk_sp<SkImage> mImage; + + // Friend TestUtils serves as a proxy for any test cases that require access to private members. + friend class TestUtils; }; } /* namespace uirenderer */ diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index f2282e661535..1a47db5c8ec2 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -229,10 +229,10 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { // TODO should we let the bound of the drawable do this for us? const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds); - auto clipBounds = canvas->getLocalClipBounds(); - SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height()); - SkIPoint offset = SkIPoint::Make(0.0f, 0.0f); if (!quickRejected) { + auto clipBounds = canvas->getLocalClipBounds(); + SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height()); + SkIPoint offset = SkIPoint::Make(0.0f, 0.0f); SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl(); const LayerProperties& layerProperties = properties.layerProperties(); // composing a hardware layer diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 75865c751d52..9d5c13e5cd75 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -16,6 +16,7 @@ #pragma once +#include <AutoBackendTextureRelease.h> #include <DisplayList.h> #include <Matrix.h> #include <Properties.h> @@ -293,6 +294,11 @@ public: static SkRect getClipBounds(const SkCanvas* canvas); static SkRect getLocalClipBounds(const SkCanvas* canvas); + static int getUsageCount(const AutoBackendTextureRelease* textureRelease) { + EXPECT_NE(nullptr, textureRelease); + return textureRelease->mUsageCount; + } + struct CallCounts { int sync = 0; int contextDestroyed = 0; diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp new file mode 100644 index 000000000000..2ec78a429481 --- /dev/null +++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "AutoBackendTextureRelease.h" +#include "tests/common/TestUtils.h" + +using namespace android; +using namespace android::uirenderer; + +AHardwareBuffer* allocHardwareBuffer() { + AHardwareBuffer* buffer; + AHardwareBuffer_Desc desc = { + .width = 16, + .height = 16, + .layers = 1, + .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM, + .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY, + }; + constexpr int kSucceeded = 0; + int status = AHardwareBuffer_allocate(&desc, &buffer); + EXPECT_EQ(kSucceeded, status); + return buffer; +} + +// Expands to AutoBackendTextureRelease_makeImage_invalid_RenderThreadTest, +// set as friend in AutoBackendTextureRelease.h +RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) { + AHardwareBuffer* buffer = allocHardwareBuffer(); + AutoBackendTextureRelease* textureRelease = + new AutoBackendTextureRelease(renderThread.getGrContext(), buffer); + + EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease)); + + // SkImage::MakeFromTexture should fail if given null GrDirectContext. + textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr); + + EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease)); + + textureRelease->unref(true); + AHardwareBuffer_release(buffer); +} + +// Expands to AutoBackendTextureRelease_makeImage_valid_RenderThreadTest, +// set as friend in AutoBackendTextureRelease.h +RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_valid) { + AHardwareBuffer* buffer = allocHardwareBuffer(); + AutoBackendTextureRelease* textureRelease = + new AutoBackendTextureRelease(renderThread.getGrContext(), buffer); + + EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease)); + + textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, renderThread.getGrContext()); + + EXPECT_EQ(2, TestUtils::getUsageCount(textureRelease)); + + textureRelease->unref(true); + AHardwareBuffer_release(buffer); +} diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java index b38f9ea39136..98578b219413 100644 --- a/location/java/android/location/GnssCapabilities.java +++ b/location/java/android/location/GnssCapabilities.java @@ -24,6 +24,8 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; @@ -123,20 +125,23 @@ public final class GnssCapabilities implements Parcelable { * @hide */ public static GnssCapabilities empty() { - return new GnssCapabilities(0, 0, 0); + return new GnssCapabilities(0, 0, 0, new ArrayList<>()); } private final @TopHalCapabilityFlags int mTopFlags; private final @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags; private final @SubHalPowerCapabilityFlags int mPowerFlags; + private final @NonNull List<GnssSignalType> mGnssSignalTypes; private GnssCapabilities( @TopHalCapabilityFlags int topFlags, @SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags, - @SubHalPowerCapabilityFlags int powerFlags) { + @SubHalPowerCapabilityFlags int powerFlags, + @NonNull List<GnssSignalType> gnssSignalTypes) { mTopFlags = topFlags; mMeasurementCorrectionsFlags = measurementCorrectionsFlags; mPowerFlags = powerFlags; + mGnssSignalTypes = gnssSignalTypes; } /** @@ -148,7 +153,8 @@ public final class GnssCapabilities implements Parcelable { if (mTopFlags == flags) { return this; } else { - return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags); + return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags, + new ArrayList<>(mGnssSignalTypes)); } } @@ -163,7 +169,8 @@ public final class GnssCapabilities implements Parcelable { if (mMeasurementCorrectionsFlags == flags) { return this; } else { - return new GnssCapabilities(mTopFlags, flags, mPowerFlags); + return new GnssCapabilities(mTopFlags, flags, mPowerFlags, + new ArrayList<>(mGnssSignalTypes)); } } @@ -177,7 +184,8 @@ public final class GnssCapabilities implements Parcelable { if (mPowerFlags == flags) { return this; } else { - return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags); + return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags, + new ArrayList<>(mGnssSignalTypes)); } } @@ -424,6 +432,14 @@ public final class GnssCapabilities implements Parcelable { return (mPowerFlags & SUB_HAL_POWER_CAPABILITY_OTHER_MODES) != 0; } + /** + * Returns the list of {@link GnssSignalType}s that the GNSS chipset supports. + */ + @NonNull + public List<GnssSignalType> getGnssSignalTypes() { + return mGnssSignalTypes; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -441,14 +457,15 @@ public final class GnssCapabilities implements Parcelable { @Override public int hashCode() { - return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags); + return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes); } public static final @NonNull Creator<GnssCapabilities> CREATOR = new Creator<GnssCapabilities>() { @Override public GnssCapabilities createFromParcel(Parcel in) { - return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt()); + return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt(), + in.createTypedArrayList(GnssSignalType.CREATOR)); } @Override @@ -467,6 +484,7 @@ public final class GnssCapabilities implements Parcelable { parcel.writeInt(mTopFlags); parcel.writeInt(mMeasurementCorrectionsFlags); parcel.writeInt(mPowerFlags); + parcel.writeTypedList(mGnssSignalTypes); } @Override @@ -545,6 +563,9 @@ public final class GnssCapabilities implements Parcelable { if (hasPowerOtherModes()) { builder.append("OTHER_MODES_POWER "); } + if (!mGnssSignalTypes.isEmpty()) { + builder.append("signalTypes=").append(mGnssSignalTypes).append(" "); + } if (builder.length() > 1) { builder.setLength(builder.length() - 1); } else { @@ -562,17 +583,20 @@ public final class GnssCapabilities implements Parcelable { private @TopHalCapabilityFlags int mTopFlags; private @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags; private @SubHalPowerCapabilityFlags int mPowerFlags; + private @NonNull List<GnssSignalType> mGnssSignalTypes; public Builder() { mTopFlags = 0; mMeasurementCorrectionsFlags = 0; mPowerFlags = 0; + mGnssSignalTypes = new ArrayList<>(); } public Builder(@NonNull GnssCapabilities capabilities) { mTopFlags = capabilities.mTopFlags; mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags; mPowerFlags = capabilities.mPowerFlags; + mGnssSignalTypes = capabilities.mGnssSignalTypes; } /** @@ -779,10 +803,19 @@ public final class GnssCapabilities implements Parcelable { } /** + * Sets a list of {@link GnssSignalType}. + */ + public @NonNull Builder setGnssSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) { + mGnssSignalTypes = gnssSignalTypes; + return this; + } + + /** * Builds a new GnssCapabilities. */ public @NonNull GnssCapabilities build() { - return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags); + return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, + new ArrayList<>(mGnssSignalTypes)); } private static int setFlag(int value, int flag, boolean set) { diff --git a/location/java/android/location/GnssSignalType.aidl b/location/java/android/location/GnssSignalType.aidl new file mode 100644 index 000000000000..1c43fe5f93c9 --- /dev/null +++ b/location/java/android/location/GnssSignalType.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable GnssSignalType; diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java new file mode 100644 index 000000000000..f9c6e725cc46 --- /dev/null +++ b/location/java/android/location/GnssSignalType.java @@ -0,0 +1,152 @@ +/* + * 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; + +import android.annotation.FloatRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * This class represents a GNSS signal type. + */ +public final class GnssSignalType implements Parcelable { + + /** + * Creates a {@link GnssSignalType} with a full list of parameters. + * + * @param constellationType the constellation type as defined in + * {@link GnssStatus.ConstellationType} + * @param carrierFrequencyHz the carrier frequency in Hz + * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()} + */ + @NonNull + public static GnssSignalType create(@GnssStatus.ConstellationType int constellationType, + @FloatRange(from = 0.0f, fromInclusive = false) double carrierFrequencyHz, + @NonNull String codeType) { + Preconditions.checkArgument(carrierFrequencyHz > 0, + "carrierFrequencyHz must be greater than 0."); + Objects.requireNonNull(codeType); + return new GnssSignalType(constellationType, carrierFrequencyHz, codeType); + } + + @GnssStatus.ConstellationType + private final int mConstellationType; + @FloatRange(from = 0.0f, fromInclusive = false) + private final double mCarrierFrequencyHz; + @NonNull + private final String mCodeType; + + /** + * Creates a {@link GnssSignalType} with a full list of parameters. + */ + private GnssSignalType(@GnssStatus.ConstellationType int constellationType, + double carrierFrequencyHz, @NonNull String codeType) { + this.mConstellationType = constellationType; + this.mCarrierFrequencyHz = carrierFrequencyHz; + this.mCodeType = codeType; + } + + /** Returns the {@link GnssStatus.ConstellationType}. */ + @GnssStatus.ConstellationType + public int getConstellationType() { + return mConstellationType; + } + + /** Returns the carrier frequency in Hz. */ + @FloatRange(from = 0.0f, fromInclusive = false) + public double getCarrierFrequencyHz() { + return mCarrierFrequencyHz; + } + + /** + * Return the code type. + * + * @see GnssMeasurement#getCodeType() + */ + @NonNull + public String getCodeType() { + return mCodeType; + } + + @NonNull + public static final Parcelable.Creator<GnssSignalType> CREATOR = + new Parcelable.Creator<GnssSignalType>() { + @Override + @NonNull + public GnssSignalType createFromParcel(@NonNull Parcel parcel) { + return new GnssSignalType(parcel.readInt(), parcel.readDouble(), + parcel.readString()); + } + + @Override + public GnssSignalType[] newArray(int i) { + return new GnssSignalType[i]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel parcel, int flags) { + parcel.writeInt(mConstellationType); + parcel.writeDouble(mCarrierFrequencyHz); + parcel.writeString(mCodeType); + } + + @NonNull + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("GnssSignalType["); + s.append(" Constellation=").append(mConstellationType); + s.append(", CarrierFrequencyHz=").append(mCarrierFrequencyHz); + s.append(", CodeType=").append(mCodeType); + s.append(']'); + return s.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof GnssSignalType) { + GnssSignalType other = (GnssSignalType) obj; + return mConstellationType == other.mConstellationType + && Double.compare(mCarrierFrequencyHz, other.mCarrierFrequencyHz) == 0 + && mCodeType.equals(other.mCodeType); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mConstellationType, mCarrierFrequencyHz, mCodeType); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java index 819358b49754..980f63b2ae6c 100644 --- a/media/java/android/media/AudioPlaybackConfiguration.java +++ b/media/java/android/media/AudioPlaybackConfiguration.java @@ -23,6 +23,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.os.Binder; import android.os.IBinder; @@ -220,46 +221,59 @@ public final class AudioPlaybackConfiguration implements Parcelable { /** * @hide - * Mute state used for anonymization. + * Mute state used for the initial state and when API is accessed without permission. */ - public static final int PLAYER_MUTE_INVALID = -1; + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_UNKNOWN = -1; /** * @hide * Flag used when muted by master volume. */ - public static final int PLAYER_MUTE_MASTER = (1 << 0); + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_MASTER = (1 << 0); /** * @hide * Flag used when muted by stream volume. */ - public static final int PLAYER_MUTE_STREAM_VOLUME = (1 << 1); + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_STREAM_VOLUME = (1 << 1); /** * @hide * Flag used when muted by stream mute. */ - public static final int PLAYER_MUTE_STREAM_MUTED = (1 << 2); + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_STREAM_MUTED = (1 << 2); /** * @hide - * Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO. + * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO. */ - public static final int PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3); + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_APP_OPS = (1 << 3); /** * @hide * Flag used when muted by client volume. */ - public static final int PLAYER_MUTE_CLIENT_VOLUME = (1 << 4); + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_CLIENT_VOLUME = (1 << 4); /** * @hide * Flag used when muted by volume shaper. */ - public static final int PLAYER_MUTE_VOLUME_SHAPER = (1 << 5); + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5); /** @hide */ @IntDef( flag = true, - value = {PLAYER_MUTE_MASTER, PLAYER_MUTE_STREAM_VOLUME, PLAYER_MUTE_STREAM_MUTED, - PLAYER_MUTE_PLAYBACK_RESTRICTED, PLAYER_MUTE_CLIENT_VOLUME, - PLAYER_MUTE_VOLUME_SHAPER}) + value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED, + MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER}) @Retention(RetentionPolicy.SOURCE) public @interface PlayerMuteEvent { } @@ -303,7 +317,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { mPlayerType = pic.mPlayerType; mClientUid = uid; mClientPid = pid; - mMutedState = PLAYER_MUTE_INVALID; + mMutedState = MUTED_BY_UNKNOWN; mDeviceId = PLAYER_DEVICEID_INVALID; mPlayerState = PLAYER_STATE_IDLE; mPlayerAttr = pic.mAttributes; @@ -352,7 +366,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { anonymCopy.mPlayerAttr = builder.build(); anonymCopy.mDeviceId = in.mDeviceId; // anonymized data - anonymCopy.mMutedState = PLAYER_MUTE_INVALID; + anonymCopy.mMutedState = MUTED_BY_UNKNOWN; anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN; anonymCopy.mClientUid = PLAYER_UPID_INVALID; anonymCopy.mClientPid = PLAYER_UPID_INVALID; @@ -413,9 +427,27 @@ public final class AudioPlaybackConfiguration implements Parcelable { /** * @hide - * @return the mute state as a combination of {@link PlayerMuteEvent} flags + * Used for determining if the current player is muted. + * <br>Note that if this result is true then {@link #getMutedBy} will be > 0. + * @return {@code true} if the player associated with this configuration has been muted (by any + * given MUTED_BY_* source event) or {@code false} otherwise. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public boolean isMuted() { + return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN; + } + + /** + * @hide + * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags. + * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A + * value of 0 represents a player which is not muted. + * @return the mute state. */ - @PlayerMuteEvent public int getMutedState() { + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @PlayerMuteEvent public int getMutedBy() { return mMutedState; } @@ -569,6 +601,17 @@ public final class AudioPlaybackConfiguration implements Parcelable { } } + private boolean isMuteAffectingActiveState() { + if (mMutedState == MUTED_BY_UNKNOWN) { + // mute state is not set, therefore it will not affect the active state + return false; + } + + return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0 + || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0 + || (mMutedState & MUTED_BY_APP_OPS) != 0; + } + /** * @hide * Returns true if the player is considered "active", i.e. actively playing with unmuted @@ -580,8 +623,7 @@ public final class AudioPlaybackConfiguration implements Parcelable { public boolean isActive() { switch (mPlayerState) { case PLAYER_STATE_STARTED: - return mMutedState == 0 - || mMutedState == PLAYER_MUTE_INVALID; // only send true if not muted + return !isMuteAffectingActiveState(); case PLAYER_STATE_UNKNOWN: case PLAYER_STATE_RELEASED: case PLAYER_STATE_IDLE: @@ -684,27 +726,27 @@ public final class AudioPlaybackConfiguration implements Parcelable { "/").append(mClientPid).append(" state:").append( toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append( " sessionId:").append(mSessionId).append(" mutedState:"); - if (mMutedState == PLAYER_MUTE_INVALID) { - apcToString.append("invalid "); + if (mMutedState == MUTED_BY_UNKNOWN) { + apcToString.append("unknown "); } else if (mMutedState == 0) { apcToString.append("none "); } else { - if ((mMutedState & PLAYER_MUTE_MASTER) != 0) { + if ((mMutedState & MUTED_BY_MASTER) != 0) { apcToString.append("master "); } - if ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) { + if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) { apcToString.append("streamVolume "); } - if ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) { + if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) { apcToString.append("streamMute "); } - if ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) { - apcToString.append("playbackRestricted "); + if ((mMutedState & MUTED_BY_APP_OPS) != 0) { + apcToString.append("appOps "); } - if ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) { + if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) { apcToString.append("clientVolume "); } - if ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0) { + if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) { apcToString.append("volumeShaper "); } } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 26cb9f8e9ee1..b4b908dbff16 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -104,8 +104,8 @@ public final class MediaRouter2 { private final String mPackageName; /** - * Stores the latest copy of all routes received from {@link MediaRouter2ServiceImpl}, without - * any filtering, sorting, or deduplication. + * Stores the latest copy of all routes received from the system server, without any filtering, + * sorting, or deduplication. * * <p>Uses {@link MediaRoute2Info#getId()} to set each entry's key. */ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 95599bd41e8d..1183ca3b4977 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -174,6 +174,12 @@ static struct { jfieldID typeId; } gDescriptorInfo; +static struct { + jclass clazz; + jmethodID ctorId; + jmethodID setId; +} gBufferInfo; + struct fields_t { jmethodID postEventFromNativeID; jmethodID lockAndGetContextID; @@ -460,11 +466,7 @@ status_t JMediaCodec::dequeueOutputBuffer( return err; } - ScopedLocalRef<jclass> clazz( - env, env->FindClass("android/media/MediaCodec$BufferInfo")); - - jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); - env->CallVoidMethod(bufferInfo, method, (jint)offset, (jint)size, timeUs, flags); + env->CallVoidMethod(bufferInfo, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags); return OK; } @@ -1091,13 +1093,7 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { CHECK(msg->findInt64("timeUs", &timeUs)); CHECK(msg->findInt32("flags", (int32_t *)&flags)); - ScopedLocalRef<jclass> clazz( - env, env->FindClass("android/media/MediaCodec$BufferInfo")); - jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V"); - jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); - - obj = env->NewObject(clazz.get(), ctor); - + obj = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId); if (obj == NULL) { if (env->ExceptionCheck()) { ALOGE("Could not create MediaCodec.BufferInfo."); @@ -1107,7 +1103,7 @@ void JMediaCodec::handleCallback(const sp<AMessage> &msg) { return; } - env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags); + env->CallVoidMethod(obj, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags); break; } @@ -3235,6 +3231,16 @@ static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) { gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I"); CHECK(gDescriptorInfo.typeId != NULL); + + clazz.reset(env->FindClass("android/media/MediaCodec$BufferInfo")); + CHECK(clazz.get() != NULL); + gBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get()); + + gBufferInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V"); + CHECK(gBufferInfo.ctorId != NULL); + + gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V"); + CHECK(gBufferInfo.setId != NULL); } static void android_media_MediaCodec_native_setup( diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp index 6990ad0fbd7d..62ffe3898667 100644 --- a/packages/CarrierDefaultApp/Android.bp +++ b/packages/CarrierDefaultApp/Android.bp @@ -13,4 +13,9 @@ android_app { libs: ["SliceStore"], platform_apis: true, certificate: "platform", + optimize: { + proguard_flags_files: [ + "proguard.flags", + ], + }, } diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.html b/packages/CarrierDefaultApp/assets/slice_store_test.html new file mode 100644 index 000000000000..7ddbd2d5f245 --- /dev/null +++ b/packages/CarrierDefaultApp/assets/slice_store_test.html @@ -0,0 +1,78 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="description" content=" + This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of + SliceStoreWebInterface. Test SliceStore APIs using ADB shell commands and the APIs below: + + FROM TERMINAL: + Allow device to override carrier configs: + $ adb root + Set PREMIUM_CAPABILITY_PRIORITIZE_LATENCY enabled: + $ adb shell cmd phone cc set-value -p supported_premium_capabilities_int_array 34 + Set the carrier purchase URL to this test HTML file: + $ adb shell cmd phone cc set-value -p premium_capability_purchase_url_string \ + file:///android_asset/slice_store_test.html + OPTIONAL: Allow premium capability purchase on LTE: + $ adb shell cmd phone cc set-value -p premium_capability_supported_on_lte_bool true + OPTIONAL: Override ServiceState to fake a NR SA connection: + $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 20 + + FROM TEST ACTIVITY: + TelephonyManager tm = getApplicationContext().getSystemService(TelephonyManager.class) + tm.isPremiumCapabilityAvailable(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY); + LinkedBlockingQueue<Integer> purchaseRequests = new LinkedBlockingQueue<>(); + tm.purchasePremiumCapability(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY, + this.getMainExecutor(), request::offer); + + When the test application starts, this HTML will be loaded into the WebView along with the + associated JavaScript functions in file:///android_asset/slice_store_test.js. + Click on the buttons in the HTML to call the corresponding @JavascriptInterface APIs. + + RESET DEVICE STATE: + Clear carrier configurations that were set: + $ adb shell cmd phone cc clear-values + Clear ServiceState override that was set: + $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset + "> + <title>Test SliceStoreActivity</title> + <script type="text/javascript" src="slice_store_test.js"></script> +</head> +<body> + <h1>Test SliceStoreActivity</h1> + <h2>Get requested premium capability</h2> + <button type="button" onclick="testGetRequestedCapability()"> + Get requested premium capability + </button> + <p id="requested_capability"></p> + + <h2>Notify purchase successful</h2> + <button type="button" onclick="testNotifyPurchaseSuccessful(60000)"> + Notify purchase successful for 1 minute + </button> + <p id="purchase_successful"></p> + + <h2>Notify purchase failed</h2> + <button type="button" onclick="testNotifyPurchaseFailed()"> + Notify purchase failed + </button> + <p id="purchase_failed"></p> +</body> +</html> diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.js b/packages/CarrierDefaultApp/assets/slice_store_test.js new file mode 100644 index 000000000000..f12a6daf8de3 --- /dev/null +++ b/packages/CarrierDefaultApp/assets/slice_store_test.js @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function testGetRequestedCapability() { + let capability = SliceStoreWebInterface.getRequestedCapability(); + document.getElementById("requested_capability").innerHTML = + "Premium capability requested: " + capability; +} + +function testNotifyPurchaseSuccessful(duration_ms_long = 0) { + SliceStoreWebInterface.notifyPurchaseSuccessful(duration); + document.getElementById("purchase_successful").innerHTML = + "Notified purchase success for duration: " + duration; +} + +function testNotifyPurchaseFailed() { + SliceStoreWebInterface.notifyPurchaseFailed(); + document.getElementById("purchase_failed").innerHTML = + "Notified purchase failed."; +} diff --git a/packages/CarrierDefaultApp/proguard.flags b/packages/CarrierDefaultApp/proguard.flags new file mode 100644 index 000000000000..64fec2ccbef2 --- /dev/null +++ b/packages/CarrierDefaultApp/proguard.flags @@ -0,0 +1,4 @@ +# Keep classes and methods that have the @JavascriptInterface annotation +-keepclassmembers class * { + @android.webkit.JavascriptInterface <methods>; +} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java index 602e31cbd130..348e3895690c 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java @@ -20,47 +20,63 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.util.Log; +import android.view.KeyEvent; import android.webkit.WebView; import com.android.phone.slicestore.SliceStore; import java.net.MalformedURLException; import java.net.URL; +import java.util.concurrent.TimeUnit; /** * Activity that launches when the user clicks on the network boost notification. + * This will open a {@link WebView} for the carrier website to allow the user to complete the + * premium capability purchase. + * The carrier website can get the requested premium capability using the JavaScript interface + * method {@code SliceStoreWebInterface.getRequestedCapability()}. + * If the purchase is successful, the carrier website shall notify SliceStore using the JavaScript + * interface method {@code SliceStoreWebInterface.notifyPurchaseSuccessful(duration)}, where + * {@code duration} is the duration of the network boost. + * If the purchase was not successful, the carrier website shall notify SliceStore using the + * JavaScript interface method {@code SliceStoreWebInterface.notifyPurchaseFailed()}. + * If either of these notification methods are not called, the purchase cannot be completed + * successfully and the purchase request will eventually time out. */ public class SliceStoreActivity extends Activity { private static final String TAG = "SliceStoreActivity"; - private URL mUrl; - private WebView mWebView; - private int mPhoneId; + private @NonNull WebView mWebView; + private @NonNull Context mApplicationContext; private int mSubId; - private @TelephonyManager.PremiumCapability int mCapability; + @TelephonyManager.PremiumCapability protected int mCapability; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); - mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID, - SubscriptionManager.INVALID_PHONE_INDEX); mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY, SliceStore.PREMIUM_CAPABILITY_INVALID); - mUrl = getUrl(); - logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability=" + mApplicationContext = getApplicationContext(); + URL url = getUrl(); + logd("onCreate: subId=" + mSubId + ", capability=" + TelephonyManager.convertPremiumCapabilityToString(mCapability) - + ", mUrl=" + mUrl); - getApplicationContext().getSystemService(NotificationManager.class) + + ", url=" + url); + + // Cancel network boost notification + mApplicationContext.getSystemService(NotificationManager.class) .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability); + + // Verify intent and values are valid if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) { loge("Not starting SliceStoreActivity with an invalid Intent: " + intent); SliceStoreBroadcastReceiver.sendSliceStoreResponse( @@ -68,10 +84,15 @@ public class SliceStoreActivity extends Activity { finishAndRemoveTask(); return; } - if (mUrl == null) { - loge("Unable to create a URL from carrier configs."); - SliceStoreBroadcastReceiver.sendSliceStoreResponse( - intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR); + if (url == null) { + String error = "Unable to create a URL from carrier configs."; + loge(error); + Intent data = new Intent(); + data.putExtra(SliceStore.EXTRA_FAILURE_CODE, + SliceStore.FAILURE_CODE_CARRIER_URL_UNAVAILABLE); + data.putExtra(SliceStore.EXTRA_FAILURE_REASON, error); + SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData( + mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data); finishAndRemoveTask(); return; } @@ -83,12 +104,53 @@ public class SliceStoreActivity extends Activity { return; } + // Create a reference to this activity in SliceStoreBroadcastReceiver SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this); + // Create and configure WebView mWebView = new WebView(this); + // Enable JavaScript for the carrier purchase website to send results back to SliceStore + mWebView.getSettings().setJavaScriptEnabled(true); + mWebView.addJavascriptInterface(new SliceStoreWebInterface(this), "SliceStoreWebInterface"); + + // Display WebView setContentView(mWebView); - mWebView.loadUrl(mUrl.toString()); - // TODO(b/245882601): Get back response from WebView + mWebView.loadUrl(url.toString()); + } + + protected void onPurchaseSuccessful(long duration) { + logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium " + + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability) + + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes."); + Intent intent = new Intent(); + intent.putExtra(SliceStore.EXTRA_PURCHASE_DURATION, duration); + SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData( + mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_SUCCESS, intent); + finishAndRemoveTask(); + } + + protected void onPurchaseFailed(@SliceStore.FailureCode int failureCode, + @Nullable String failureReason) { + logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability " + + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: " + + SliceStore.convertFailureCodeToString(failureCode) + " and reason: " + + failureReason); + Intent data = new Intent(); + data.putExtra(SliceStore.EXTRA_FAILURE_CODE, failureCode); + data.putExtra(SliceStore.EXTRA_FAILURE_REASON, failureReason); + SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData( + mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data); + finishAndRemoveTask(); + } + + @Override + public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { + // Pressing back in the WebView will go to the previous page instead of closing SliceStore. + if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) { + mWebView.goBack(); + return true; + } + return super.onKeyDown(keyCode, event); } @Override @@ -100,8 +162,8 @@ public class SliceStoreActivity extends Activity { super.onDestroy(); } - private @Nullable URL getUrl() { - String url = getApplicationContext().getSystemService(CarrierConfigManager.class) + @Nullable private URL getUrl() { + String url = mApplicationContext.getSystemService(CarrierConfigManager.class) .getConfigForSubId(mSubId).getString( CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING); try { diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java index 7eb851dcdd58..7867ef1c6516 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.drawable.Icon; import android.os.UserHandle; +import android.telephony.AnomalyReporter; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; @@ -37,6 +38,7 @@ import com.android.phone.slicestore.SliceStore; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; +import java.util.UUID; /** * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the @@ -47,6 +49,12 @@ import java.util.Map; public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ private static final String TAG = "SliceStoreBroadcastReceiver"; + /** + * UUID to report an anomaly when receiving a PendingIntent from an application or process + * other than the Phone process. + */ + private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53"; + /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */ private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities = new HashMap<>(); @@ -102,6 +110,28 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ } /** + * Send the PendingIntent containing the corresponding SliceStore response with additional data. + * + * @param context The Context to use to send the PendingIntent. + * @param intent The Intent containing the PendingIntent extra. + * @param extra The extra to get the PendingIntent to send. + * @param data The Intent containing additional data to send with the PendingIntent. + */ + public static void sendSliceStoreResponseWithData(@NonNull Context context, + @NonNull Intent intent, @NonNull String extra, @NonNull Intent data) { + PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class); + if (pendingIntent == null) { + loge("PendingIntent does not exist for extra: " + extra); + return; + } + try { + pendingIntent.send(context, 0 /* unused */, data); + } catch (PendingIntent.CanceledException e) { + loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e); + } + } + + /** * Check whether the Intent is valid and can be used to complete purchases in the SliceStore. * This checks that all necessary extras exist and that the values are valid. * @@ -139,7 +169,8 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED) && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR) && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED) - && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA); + && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA) + && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_SUCCESS); } private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) { @@ -148,12 +179,20 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ if (pendingIntent == null) { loge("isPendingIntentValid: " + intentType + " intent not found."); return false; - } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) { - return true; } - loge("isPendingIntentValid: " + intentType + " intent was created by " - + pendingIntent.getCreatorPackage() + " instead of the phone process."); - return false; + String creatorPackage = pendingIntent.getCreatorPackage(); + if (!creatorPackage.equals(TelephonyManager.PHONE_PROCESS_NAME)) { + String logStr = "isPendingIntentValid: " + intentType + " intent was created by " + + creatorPackage + " instead of the phone process."; + loge(logStr); + AnomalyReporter.reportAnomaly(UUID.fromString(UUID_BAD_PENDING_INTENT), logStr); + return false; + } + if (!pendingIntent.isBroadcast()) { + loge("isPendingIntentValid: " + intentType + " intent is not a broadcast."); + return false; + } + return true; } @NonNull private static String getPendingIntentType(@NonNull String extra) { @@ -162,6 +201,7 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error"; case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed"; case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data"; + case SliceStore.EXTRA_INTENT_SUCCESS: return "success"; default: { loge("Unknown pending intent extra: " + extra); return "unknown(" + extra + ")"; @@ -292,7 +332,6 @@ public class SliceStoreBroadcastReceiver extends BroadcastReceiver{ logd("Closing SliceStore WebView since the user did not complete the purchase " + "in time."); sSliceStoreActivities.get(capability).get().finishAndRemoveTask(); - // TODO: Display a toast to indicate timeout for better UX? } } diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java new file mode 100644 index 000000000000..ab5d0809a1f6 --- /dev/null +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java @@ -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.carrierdefaultapp; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.telephony.TelephonyManager; +import android.webkit.JavascriptInterface; + +import com.android.phone.slicestore.SliceStore; + +/** + * SliceStore web interface class allowing carrier websites to send responses back to SliceStore + * using JavaScript. + */ +public class SliceStoreWebInterface { + @NonNull SliceStoreActivity mActivity; + + public SliceStoreWebInterface(@NonNull SliceStoreActivity activity) { + mActivity = activity; + } + /** + * Interface method allowing the carrier website to get the premium capability + * that was requested to purchase. + * + * This can be called using the JavaScript below: + * <script type="text/javascript"> + * function getRequestedCapability(duration) { + * SliceStoreWebInterface.getRequestedCapability(); + * } + * </script> + */ + @JavascriptInterface + @TelephonyManager.PremiumCapability public int getRequestedCapability() { + return mActivity.mCapability; + } + + /** + * Interface method allowing the carrier website to notify the SliceStore of a successful + * premium capability purchase and the duration for which the premium capability is purchased. + * + * This can be called using the JavaScript below: + * <script type="text/javascript"> + * function notifyPurchaseSuccessful(duration_ms_long = 0) { + * SliceStoreWebInterface.notifyPurchaseSuccessful(duration_ms_long); + * } + * </script> + * + * @param duration The duration for which the premium capability is purchased in milliseconds. + */ + @JavascriptInterface + public void notifyPurchaseSuccessful(long duration) { + mActivity.onPurchaseSuccessful(duration); + } + + /** + * Interface method allowing the carrier website to notify the SliceStore of a failed + * premium capability purchase. + * + * This can be called using the JavaScript below: + * <script type="text/javascript"> + * function notifyPurchaseFailed() { + * SliceStoreWebInterface.notifyPurchaseFailed(); + * } + * </script> + * + * @param failureCode The failure code. + * @param failureReason If the failure code is {@link SliceStore#FAILURE_CODE_UNKNOWN}, + * the human-readable reason for failure. + */ + @JavascriptInterface + public void notifyPurchaseFailed(@SliceStore.FailureCode int failureCode, + @Nullable String failureReason) { + mActivity.onPurchaseFailed(failureCode, failureReason); + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index 56fb1a91aa90..01348e47932c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -23,6 +23,8 @@ import android.content.Intent import android.credentials.CreateCredentialRequest import android.credentials.ui.Constants import android.credentials.ui.Entry +import android.credentials.ui.CreateCredentialProviderData +import android.credentials.ui.GetCredentialProviderData import android.credentials.ui.ProviderData import android.credentials.ui.RequestInfo import android.credentials.ui.BaseDialogResult @@ -36,7 +38,7 @@ import com.android.credentialmanager.createflow.CreateScreenState import com.android.credentialmanager.createflow.RequestDisplayInfo import com.android.credentialmanager.getflow.GetCredentialUiState import com.android.credentialmanager.getflow.GetScreenState -import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL +import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL // Consider repo per screen, similar to view model? class CredentialManagerRepo( @@ -54,10 +56,22 @@ class CredentialManagerRepo( RequestInfo::class.java ) ?: testRequestInfo() - providerList = intent.extras?.getParcelableArrayList( - ProviderData.EXTRA_PROVIDER_DATA_LIST, - ProviderData::class.java - ) ?: testProviderList() + providerList = when (requestInfo.type) { + RequestInfo.TYPE_CREATE -> + intent.extras?.getParcelableArrayList( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, + CreateCredentialProviderData::class.java + ) ?: testCreateCredentialProviderList() + RequestInfo.TYPE_GET -> + intent.extras?.getParcelableArrayList( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, + GetCredentialProviderData::class.java + ) ?: testGetCredentialProviderList() + else -> { + // TODO: fail gracefully + throw IllegalStateException("Unrecognized request type: ${requestInfo.type}") + } + } resultReceiver = intent.getParcelableExtra( Constants.EXTRA_RESULT_RECEIVER, @@ -84,7 +98,9 @@ class CredentialManagerRepo( } fun getCredentialInitialUiState(): GetCredentialUiState { - val providerList = GetFlowUtils.toProviderList(providerList, context) + val providerList = GetFlowUtils.toProviderList( + // TODO: handle runtime cast error + providerList as List<GetCredentialProviderData>, context) // TODO: covert from real requestInfo val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo( "Elisa Beckett", @@ -100,7 +116,9 @@ class CredentialManagerRepo( } fun createPasskeyInitialUiState(): CreatePasskeyUiState { - val providerList = CreateFlowUtils.toProviderList(providerList, context) + val providerList = CreateFlowUtils.toProviderList( + // Handle runtime cast error + providerList as List<CreateCredentialProviderData>, context) // TODO: covert from real requestInfo val requestDisplayInfo = RequestDisplayInfo( "Elisa Beckett", @@ -130,31 +148,64 @@ class CredentialManagerRepo( } // TODO: below are prototype functionalities. To be removed for productionization. - private fun testProviderList(): List<ProviderData> { + private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> { return listOf( - ProviderData.Builder( - "com.google", - "Google Password Manager", - Icon.createWithResource(context, R.drawable.ic_launcher_foreground)) - .setCredentialEntries( + CreateCredentialProviderData.Builder("com.google/com.google.CredentialManagerService") + .setSaveEntries( listOf<Entry>( newEntry("key1", "subkey-1", "elisa.beckett@gmail.com", "Elisa Backett", "20 passwords and 7 passkeys saved"), newEntry("key1", "subkey-2", "elisa.work@google.com", "Elisa Backett Work", "20 passwords and 7 passkeys saved"), ) - ).setActionChips( + ) + .setActionChips( listOf<Entry>( newEntry("key2", "subkey-1", "Go to Settings", "", "20 passwords and 7 passkeys saved"), newEntry("key2", "subkey-2", "Switch Account", "", "20 passwords and 7 passkeys saved"), ), + ) + .setIsDefaultProvider(true) + .build(), + CreateCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService") + .setSaveEntries( + listOf<Entry>( + newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com", + "Elisa Backett", "20 passwords and 7 passkeys saved"), + newEntry("key1", "subkey-4", "elisa.work@dashlane.com", + "Elisa Backett Work", "20 passwords and 7 passkeys saved"), + ) + ).setActionChips( + listOf<Entry>( + newEntry("key2", "subkey-3", "Manage Accounts", + "Manage your accounts in the dashlane app", + "20 passwords and 7 passkeys saved"), + ), ).build(), - ProviderData.Builder( - "com.dashlane", - "Dashlane", - Icon.createWithResource(context, R.drawable.ic_launcher_foreground)) + ) + } + + private fun testGetCredentialProviderList(): List<GetCredentialProviderData> { + return listOf( + GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService") + .setCredentialEntries( + listOf<Entry>( + newEntry("key1", "subkey-1", "elisa.beckett@gmail.com", + "Elisa Backett", "20 passwords and 7 passkeys saved"), + newEntry("key1", "subkey-2", "elisa.work@google.com", + "Elisa Backett Work", "20 passwords and 7 passkeys saved"), + ) + ).setActionChips( + listOf<Entry>( + newEntry("key2", "subkey-1", "Go to Settings", "", + "20 passwords and 7 passkeys saved"), + newEntry("key2", "subkey-2", "Switch Account", "", + "20 passwords and 7 passkeys saved"), + ), + ).build(), + GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService") .setCredentialEntries( listOf<Entry>( newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com", @@ -166,7 +217,7 @@ class CredentialManagerRepo( listOf<Entry>( newEntry("key2", "subkey-3", "Manage Accounts", "Manage your accounts in the dashlane app", - "20 passwords and 7 passkeys saved"), + "20 passwords and 7 passkeys saved"), ), ).build(), ) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 2ba8748c16b7..bf0dba23cb64 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -18,7 +18,8 @@ package com.android.credentialmanager import android.content.Context import android.credentials.ui.Entry -import android.credentials.ui.ProviderData +import android.credentials.ui.GetCredentialProviderData +import android.credentials.ui.CreateCredentialProviderData import com.android.credentialmanager.createflow.CreateOptionInfo import com.android.credentialmanager.getflow.CredentialOptionInfo import com.android.credentialmanager.getflow.ProviderInfo @@ -28,7 +29,7 @@ class GetFlowUtils { companion object { fun toProviderList( - providerDataList: List<ProviderData>, + providerDataList: List<GetCredentialProviderData>, context: Context, ): List<ProviderInfo> { return providerDataList.map { @@ -36,9 +37,10 @@ class GetFlowUtils { // TODO: replace to extract from the service data structure when available icon = context.getDrawable(R.drawable.ic_passkey)!!, name = it.providerFlattenedComponentName, - displayName = it.providerDisplayName, + // TODO: get the service display name and icon from the component name. + displayName = it.providerFlattenedComponentName, credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, - credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context) + credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context), ) } } @@ -72,7 +74,7 @@ class CreateFlowUtils { companion object { fun toProviderList( - providerDataList: List<ProviderData>, + providerDataList: List<CreateCredentialProviderData>, context: Context, ): List<com.android.credentialmanager.createflow.ProviderInfo> { return providerDataList.map { @@ -80,9 +82,11 @@ class CreateFlowUtils { // TODO: replace to extract from the service data structure when available icon = context.getDrawable(R.drawable.ic_passkey)!!, name = it.providerFlattenedComponentName, - displayName = it.providerDisplayName, + // TODO: get the service display name and icon from the component name. + displayName = it.providerFlattenedComponentName, credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!, - createOptions = toCreationOptionInfoList(it.credentialEntries, context), + createOptions = toCreationOptionInfoList(it.saveEntries, context), + isDefault = it.isDefaultProvider, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index cb2bf10f2aef..db0f337e9a2b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -24,6 +24,7 @@ data class ProviderInfo( val displayName: String, val credentialTypeIcon: Drawable, val createOptions: List<CreateOptionInfo>, + val isDefault: Boolean, ) data class CreateOptionInfo( diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt index 8e30208e75d9..aeea46a85caf 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt @@ -39,8 +39,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap import com.android.credentialmanager.R -import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL -import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL +import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL +import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL import com.android.credentialmanager.ui.theme.Grey100 import com.android.credentialmanager.ui.theme.Shapes import com.android.credentialmanager.ui.theme.Typography diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt new file mode 100644 index 000000000000..7e7dbde8655a --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.credentials.Credential +import android.os.Bundle + +/** + * Base request class for registering a credential. + * + * @property type the credential type + * @property data the request data in the [Bundle] format + * @property requireSystemProvider true if must only be fulfilled by a system provider and false + * otherwise + */ +open class CreateCredentialRequest( + val type: String, + val data: Bundle, + val requireSystemProvider: Boolean, +) { + companion object { + @JvmStatic + fun createFrom(from: android.credentials.CreateCredentialRequest): CreateCredentialRequest { + return try { + when (from.type) { + Credential.TYPE_PASSWORD_CREDENTIAL -> + CreatePasswordRequest.createFrom(from.data) + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> + CreatePublicKeyCredentialBaseRequest.createFrom(from.data) + else -> + CreateCredentialRequest(from.type, from.data, from.requireSystemProvider()) + } + } catch (e: FrameworkClassParsingException) { + CreateCredentialRequest(from.type, from.data, from.requireSystemProvider()) + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt new file mode 100644 index 000000000000..f0da9f9d1866 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.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.credentialmanager.jetpack.developer + +import android.credentials.Credential +import android.os.Bundle + +/** + * A request to save the user password credential with their password provider. + * + * @property id the user id associated with the password + * @property password the password + * @throws NullPointerException If [id] is null + * @throws NullPointerException If [password] is null + * @throws IllegalArgumentException If [password] is empty + */ +class CreatePasswordRequest constructor( + val id: String, + val password: String, +) : CreateCredentialRequest( + Credential.TYPE_PASSWORD_CREDENTIAL, + toBundle(id, password), + false, +) { + + init { + require(password.isNotEmpty()) { "password should not be empty" } + } + + companion object { + const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID" + const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD" + + @JvmStatic + internal fun toBundle(id: String, password: String): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_ID, id) + bundle.putString(BUNDLE_KEY_PASSWORD, password) + return bundle + } + + @JvmStatic + fun createFrom(data: Bundle): CreatePasswordRequest { + try { + val id = data.getString(BUNDLE_KEY_ID) + val password = data.getString(BUNDLE_KEY_PASSWORD) + return CreatePasswordRequest(id!!, password!!) + } catch (e: Exception) { + throw FrameworkClassParsingException() + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt new file mode 100644 index 000000000000..26d61f9eb7a9 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * Base request class for registering a public key credential. + * + * @property requestJson The request in JSON format + * @throws NullPointerException If [requestJson] is null. This is handled by the Kotlin runtime + * @throws IllegalArgumentException If [requestJson] is empty + * + * @hide + */ +abstract class CreatePublicKeyCredentialBaseRequest constructor( + val requestJson: String, + type: String, + data: Bundle, + requireSystemProvider: Boolean, +) : CreateCredentialRequest(type, data, requireSystemProvider) { + + init { + require(requestJson.isNotEmpty()) { "request json must not be empty" } + } + + companion object { + const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" + const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE" + + @JvmStatic + fun createFrom(data: Bundle): CreatePublicKeyCredentialBaseRequest { + return when (data.getString(BUNDLE_KEY_SUBTYPE)) { + CreatePublicKeyCredentialRequest + .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST -> + CreatePublicKeyCredentialRequestPrivileged.createFrom(data) + CreatePublicKeyCredentialRequestPrivileged + .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED -> + CreatePublicKeyCredentialRequestPrivileged.createFrom(data) + else -> throw FrameworkClassParsingException() + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt new file mode 100644 index 000000000000..2eda90b827dc --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * A request to register a passkey from the user's public key credential provider. + * + * @property requestJson the request in JSON format + * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, + * true by default + * @throws NullPointerException If [requestJson] or [allowHybrid] is null. This is handled by the + * Kotlin runtime + * @throws IllegalArgumentException If [requestJson] is empty + * + * @hide + */ +class CreatePublicKeyCredentialRequest @JvmOverloads constructor( + requestJson: String, + @get:JvmName("allowHybrid") + val allowHybrid: Boolean = true +) : CreatePublicKeyCredentialBaseRequest( + requestJson, + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + toBundle(requestJson, allowHybrid), + false, +) { + companion object { + const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" + const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST" + + @JvmStatic + internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_SUBTYPE, + BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST) + bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) + bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + return bundle + } + + @JvmStatic + fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest { + try { + val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) + val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) + return CreatePublicKeyCredentialRequest(requestJson!!, (allowHybrid!!) as Boolean) + } catch (e: Exception) { + throw FrameworkClassParsingException() + } + } + } +}
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt new file mode 100644 index 000000000000..36324f83a7e5 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * A privileged request to register a passkey from the user’s public key credential provider, where + * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default + * brower, caBLE, can use this. + * + * @property requestJson the privileged request in JSON format + * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, + * true by default + * @property rp the expected true RP ID which will override the one in the [requestJson] + * @property clientDataHash a hash that is used to verify the [rp] Identity + * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] is + * null. This is handled by the Kotlin runtime + * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty + * + * @hide + */ +class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor( + requestJson: String, + val rp: String, + val clientDataHash: String, + @get:JvmName("allowHybrid") + val allowHybrid: Boolean = true +) : CreatePublicKeyCredentialBaseRequest( + requestJson, + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + toBundle(requestJson, rp, clientDataHash, allowHybrid), + false, +) { + + init { + require(rp.isNotEmpty()) { "rp must not be empty" } + require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" } + } + + /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */ + class Builder(var requestJson: String, var rp: String, var clientDataHash: String) { + + private var allowHybrid: Boolean = true + + /** + * Sets the privileged request in JSON format. + */ + fun setRequestJson(requestJson: String): Builder { + this.requestJson = requestJson + return this + } + + /** + * Sets whether hybrid credentials are allowed to fulfill this request, true by default. + */ + fun setAllowHybrid(allowHybrid: Boolean): Builder { + this.allowHybrid = allowHybrid + return this + } + + /** + * Sets the expected true RP ID which will override the one in the [requestJson]. + */ + fun setRp(rp: String): Builder { + this.rp = rp + return this + } + + /** + * Sets a hash that is used to verify the [rp] Identity. + */ + fun setClientDataHash(clientDataHash: String): Builder { + this.clientDataHash = clientDataHash + return this + } + + /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */ + fun build(): CreatePublicKeyCredentialRequestPrivileged { + return CreatePublicKeyCredentialRequestPrivileged(this.requestJson, + this.rp, this.clientDataHash, this.allowHybrid) + } + } + + companion object { + const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP" + const val BUNDLE_KEY_CLIENT_DATA_HASH = + "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" + const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" + const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" + + "PRIVILEGED" + + @JvmStatic + internal fun toBundle( + requestJson: String, + rp: String, + clientDataHash: String, + allowHybrid: Boolean + ): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_SUBTYPE, + BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED) + bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) + bundle.putString(BUNDLE_KEY_RP, rp) + bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash) + bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + return bundle + } + + @JvmStatic + fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged { + try { + val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) + val rp = data.getString(BUNDLE_KEY_RP) + val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH) + val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) + return CreatePublicKeyCredentialRequestPrivileged( + requestJson!!, + rp!!, + clientDataHash!!, + (allowHybrid!!) as Boolean, + ) + } catch (e: Exception) { + throw FrameworkClassParsingException() + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt new file mode 100644 index 000000000000..ee08e9e30649 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * Base class for a credential with which the user consented to authenticate to the app. + * + * @property type the credential type + * @property data the credential data in the [Bundle] format. + */ +open class Credential(val type: String, val data: Bundle) diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt new file mode 100644 index 000000000000..497c272750ac --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +/** + * Internal exception used to indicate a parsing error while converting from a framework type to + * a jetpack type. + * + * @hide + */ +internal class FrameworkClassParsingException : Exception()
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt new file mode 100644 index 000000000000..eb65241ed4df --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.credentials.Credential +import android.os.Bundle + +/** + * Base request class for getting a registered credential. + * + * @property type the credential type + * @property data the request data in the [Bundle] format + * @property requireSystemProvider true if must only be fulfilled by a system provider and false + * otherwise + */ +open class GetCredentialOption( + val type: String, + val data: Bundle, + val requireSystemProvider: Boolean, +) { + companion object { + @JvmStatic + fun createFrom(from: android.credentials.GetCredentialOption): GetCredentialOption { + return try { + when (from.type) { + Credential.TYPE_PASSWORD_CREDENTIAL -> + GetPasswordOption.createFrom(from.data) + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL -> + GetPublicKeyCredentialBaseOption.createFrom(from.data) + else -> + GetCredentialOption(from.type, from.data, from.requireSystemProvider()) + } + } catch (e: FrameworkClassParsingException) { + GetCredentialOption(from.type, from.data, from.requireSystemProvider()) + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt new file mode 100644 index 000000000000..7f9256ed6c75 --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +/** + * Encapsulates a request to get a user credential. + * + * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose + * one to authenticate to the app + * @throws IllegalArgumentException If [getCredentialOptions] is empty + */ +class GetCredentialRequest constructor( + val getCredentialOptions: List<GetCredentialOption>, +) { + + init { + require(getCredentialOptions.isNotEmpty()) { "credentialRequests should not be empty" } + } + + /** A builder for [GetCredentialRequest]. */ + class Builder { + private var getCredentialOptions: MutableList<GetCredentialOption> = mutableListOf() + + /** Adds a specific type of [GetCredentialOption]. */ + fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder { + getCredentialOptions.add(getCredentialOption) + return this + } + + /** Sets the list of [GetCredentialOption]. */ + fun setGetCredentialOptions(getCredentialOptions: List<GetCredentialOption>): Builder { + this.getCredentialOptions = getCredentialOptions.toMutableList() + return this + } + + /** + * Builds a [GetCredentialRequest]. + * + * @throws IllegalArgumentException If [getCredentialOptions] is empty + */ + fun build(): GetCredentialRequest { + return GetCredentialRequest(getCredentialOptions.toList()) + } + } + + companion object { + @JvmStatic + fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest { + return GetCredentialRequest( + from.getCredentialOptions.map {GetCredentialOption.createFrom(it)} + ) + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt new file mode 100644 index 000000000000..2facad17b04e --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.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.credentialmanager.jetpack.developer + +import android.credentials.Credential +import android.os.Bundle + +/** A request to retrieve the user's saved application password from their password provider. */ +class GetPasswordOption : GetCredentialOption( + Credential.TYPE_PASSWORD_CREDENTIAL, + Bundle(), + false, +) { + companion object { + @JvmStatic + fun createFrom(data: Bundle): GetPasswordOption { + return GetPasswordOption() + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt new file mode 100644 index 000000000000..9b51b306dd6b --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * Base request class for getting a registered public key credential. + * + * @property requestJson the request in JSON format + * @throws NullPointerException If [requestJson] is null - auto handled by the + * Kotlin runtime + * @throws IllegalArgumentException If [requestJson] is empty + * + * @hide + */ +abstract class GetPublicKeyCredentialBaseOption constructor( + val requestJson: String, + type: String, + data: Bundle, + requireSystemProvider: Boolean, +) : GetCredentialOption(type, data, requireSystemProvider) { + + init { + require(requestJson.isNotEmpty()) { "request json must not be empty" } + } + + companion object { + const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON" + const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE" + + @JvmStatic + fun createFrom(data: Bundle): GetPublicKeyCredentialBaseOption { + return when (data.getString(BUNDLE_KEY_SUBTYPE)) { + GetPublicKeyCredentialOption + .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION -> + GetPublicKeyCredentialOption.createFrom(data) + GetPublicKeyCredentialOptionPrivileged + .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED -> + GetPublicKeyCredentialOptionPrivileged.createFrom(data) + else -> throw FrameworkClassParsingException() + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt new file mode 100644 index 000000000000..6f13c17f9b6e --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.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.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * A request to get passkeys from the user's public key credential provider. + * + * @property requestJson the request in JSON format + * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, + * true by default + * @throws NullPointerException If [requestJson] or [allowHybrid] is null. It is handled by the + * Kotlin runtime + * @throws IllegalArgumentException If [requestJson] is empty + * + * @hide + */ +class GetPublicKeyCredentialOption @JvmOverloads constructor( + requestJson: String, + @get:JvmName("allowHybrid") + val allowHybrid: Boolean = true, +) : GetPublicKeyCredentialBaseOption( + requestJson, + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + toBundle(requestJson, allowHybrid), + false +) { + companion object { + const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" + const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" + + @JvmStatic + internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) + bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + return bundle + } + + @JvmStatic + fun createFrom(data: Bundle): GetPublicKeyCredentialOption { + try { + val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) + val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) + return GetPublicKeyCredentialOption(requestJson!!, (allowHybrid!!) as Boolean) + } catch (e: Exception) { + throw FrameworkClassParsingException() + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt new file mode 100644 index 000000000000..79c62a1cdfbe --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * A privileged request to get passkeys from the user's public key credential provider. The caller + * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE) + * can use this. + * + * @property requestJson the privileged request in JSON format + * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request, + * true by default + * @property rp the expected true RP ID which will override the one in the [requestJson] + * @property clientDataHash a hash that is used to verify the [rp] Identity + * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] + * is null. This is handled by the Kotlin runtime + * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty + * + * @hide + */ +class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor( + requestJson: String, + val rp: String, + val clientDataHash: String, + @get:JvmName("allowHybrid") + val allowHybrid: Boolean = true +) : GetPublicKeyCredentialBaseOption( + requestJson, + PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, + toBundle(requestJson, rp, clientDataHash, allowHybrid), + false, +) { + + init { + require(rp.isNotEmpty()) { "rp must not be empty" } + require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" } + } + + /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */ + class Builder(var requestJson: String, var rp: String, var clientDataHash: String) { + + private var allowHybrid: Boolean = true + + /** + * Sets the privileged request in JSON format. + */ + fun setRequestJson(requestJson: String): Builder { + this.requestJson = requestJson + return this + } + + /** + * Sets whether hybrid credentials are allowed to fulfill this request, true by default. + */ + fun setAllowHybrid(allowHybrid: Boolean): Builder { + this.allowHybrid = allowHybrid + return this + } + + /** + * Sets the expected true RP ID which will override the one in the [requestJson]. + */ + fun setRp(rp: String): Builder { + this.rp = rp + return this + } + + /** + * Sets a hash that is used to verify the [rp] Identity. + */ + fun setClientDataHash(clientDataHash: String): Builder { + this.clientDataHash = clientDataHash + return this + } + + /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */ + fun build(): GetPublicKeyCredentialOptionPrivileged { + return GetPublicKeyCredentialOptionPrivileged(this.requestJson, + this.rp, this.clientDataHash, this.allowHybrid) + } + } + + companion object { + const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP" + const val BUNDLE_KEY_CLIENT_DATA_HASH = + "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH" + const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID" + const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED = + "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" + + "_PRIVILEGED" + + @JvmStatic + internal fun toBundle( + requestJson: String, + rp: String, + clientDataHash: String, + allowHybrid: Boolean + ): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson) + bundle.putString(BUNDLE_KEY_RP, rp) + bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash) + bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid) + return bundle + } + + @JvmStatic + fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged { + try { + val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON) + val rp = data.getString(BUNDLE_KEY_RP) + val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH) + val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID) + return GetPublicKeyCredentialOptionPrivileged( + requestJson!!, + rp!!, + clientDataHash!!, + (allowHybrid!!) as Boolean, + ) + } catch (e: Exception) { + throw FrameworkClassParsingException() + } + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt new file mode 100644 index 000000000000..b45a63bcf4ec --- /dev/null +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.credentialmanager.jetpack.developer + +import android.os.Bundle + +/** + * Represents the user's passkey credential granted by the user for app sign-in. + * + * @property authenticationResponseJson the public key credential authentication response in + * JSON format that follows the standard webauthn json format shown at + * [this w3c link](https://w3c.github.io/webauthn/#dictdef-authenticationresponsejson) + * @throws NullPointerException If [authenticationResponseJson] is null. This is handled by the + * kotlin runtime + * @throws IllegalArgumentException If [authenticationResponseJson] is empty + * + * @hide + */ +class PublicKeyCredential constructor( + val authenticationResponseJson: String +) : Credential( + TYPE_PUBLIC_KEY_CREDENTIAL, + toBundle(authenticationResponseJson) +) { + + init { + require(authenticationResponseJson.isNotEmpty()) { + "authentication response JSON must not be empty" } + } + companion object { + /** The type value for public key credential related operations. */ + const val TYPE_PUBLIC_KEY_CREDENTIAL: String = + "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" + const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON = + "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON" + + @JvmStatic + internal fun toBundle(authenticationResponseJson: String): Bundle { + val bundle = Bundle() + bundle.putString(BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON, authenticationResponseJson) + return bundle + } + } +} diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt index d4341b498fe0..1e639fe6bd55 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.jetpack +package com.android.credentialmanager.jetpack.provider import android.app.slice.Slice import android.credentials.ui.Entry diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt index d6f1b5f5c8e9..12ab436e1507 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.jetpack +package com.android.credentialmanager.jetpack.provider import android.app.slice.Slice import android.graphics.drawable.Icon diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt index bb3b206500b4..c5dbe66e8dbb 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.jetpack +package com.android.credentialmanager.jetpack.provider import android.app.slice.Slice import android.credentials.ui.Entry diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt index 7311b7081343..5049503b32c1 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.jetpack +package com.android.credentialmanager.jetpack.provider import android.app.slice.Slice import android.credentials.ui.Entry diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt index fad3309fb86f..b260cf63587c 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.credentialmanager.jetpack +package com.android.credentialmanager.jetpack.provider import android.app.slice.Slice import android.credentials.ui.Entry diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index c659525d42a5..f170ead70b77 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -54,6 +54,7 @@ android_library { "SettingsLibSettingsTransition", "SettingsLibButtonPreference", "SettingsLibDeviceStateRotationLock", + "SettingsLibProfileSelector", "setupdesign", "zxing-core-1.7", "androidx.room_room-runtime", diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp new file mode 100644 index 000000000000..250cd755a6a6 --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/Android.bp @@ -0,0 +1,27 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_library { + name: "SettingsLibProfileSelector", + + srcs: ["src/**/*.java"], + resource_dirs: ["res"], + + static_libs: [ + "com.google.android.material_material", + "SettingsLibSettingsTheme", + ], + + sdk_version: "system_current", + min_sdk_version: "23", + apex_available: [ + "//apex_available:platform", + "com.android.mediaprovider", + ], +} diff --git a/packages/SettingsLib/ProfileSelector/AndroidManifest.xml b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml new file mode 100644 index 000000000000..a57469e39eb6 --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.settingslib.widget"> + + <uses-sdk android:minSdkVersion="23" /> +</manifest> diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml index 9a093601a92c..9a093601a92c 100644 --- a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml +++ b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml index 33f96df8d5b5..33f96df8d5b5 100644 --- a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml +++ b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml index 57fef52f15bd..57fef52f15bd 100644 --- a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml +++ b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml index df2346d7175e..df2346d7175e 100644 --- a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml +++ b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml index 5378eeef97e0..5378eeef97e0 100644 --- a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml +++ b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml index 9c10a5b59281..9c10a5b59281 100644 --- a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml +++ b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml diff --git a/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml b/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml new file mode 100644 index 000000000000..0448c6c4f467 --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml @@ -0,0 +1,38 @@ +<?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. + --> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:theme="@style/Theme.MaterialComponents.DayNight" + android:id="@+id/tab_container" + android:clipToPadding="true" + android:clipChildren="true" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.google.android.material.tabs.TabLayout + android:id="@+id/tabs" + style="@style/SettingsLibTabsStyle"/> + + <androidx.viewpager2.widget.ViewPager2 + android:id="@+id/view_pager" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + </androidx.viewpager2.widget.ViewPager2> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SettingsLib/ProfileSelector/res/values/strings.xml b/packages/SettingsLib/ProfileSelector/res/values/strings.xml new file mode 100644 index 000000000000..68d4047a497c --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/res/values/strings.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Header for items under the personal user [CHAR LIMIT=30] --> + <string name="settingslib_category_personal">Personal</string> + <!-- Header for items under the work user [CHAR LIMIT=30] --> + <string name="settingslib_category_work">Work</string> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/ProfileSelector/res/values/styles.xml index 0b703c99884b..0b703c99884b 100644 --- a/packages/SettingsLib/res/values-v31/styles.xml +++ b/packages/SettingsLib/ProfileSelector/res/values/styles.xml diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java new file mode 100644 index 000000000000..ac426ed8b5d4 --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.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 com.android.settingslib.widget; + +import android.app.Activity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +/** + * Base fragment class for profile settings. + */ +public abstract class ProfileSelectFragment extends Fragment { + + /** + * Personal or Work profile tab of {@link ProfileSelectFragment} + * <p>0: Personal tab. + * <p>1: Work profile tab. + */ + public static final String EXTRA_SHOW_FRAGMENT_TAB = + ":settings:show_fragment_tab"; + + /** + * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB + */ + public static final int PERSONAL_TAB = 0; + + /** + * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB + */ + public static final int WORK_TAB = 1; + + private ViewGroup mContentView; + + private ViewPager2 mViewPager; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Defines the xml file for the fragment + mContentView = (ViewGroup) inflater.inflate(R.layout.tab_fragment, container, false); + + final Activity activity = getActivity(); + final int titleResId = getTitleResId(); + if (titleResId > 0) { + activity.setTitle(titleResId); + } + final int selectedTab = getTabId(activity, getArguments()); + + final View tabContainer = mContentView.findViewById(R.id.tab_container); + mViewPager = tabContainer.findViewById(R.id.view_pager); + mViewPager.setAdapter(new ProfileViewPagerAdapter(this)); + final TabLayout tabs = tabContainer.findViewById(R.id.tabs); + new TabLayoutMediator(tabs, mViewPager, + (tab, position) -> tab.setText(getPageTitle(position)) + ).attach(); + + tabContainer.setVisibility(View.VISIBLE); + final TabLayout.Tab tab = tabs.getTabAt(selectedTab); + tab.select(); + + return mContentView; + } + + /** + * create Personal or Work profile fragment + * <p>0: Personal profile. + * <p>1: Work profile. + */ + public abstract Fragment createFragment(int position); + + /** + * Returns a resource ID of the title + * Override this if the title needs to be updated dynamically. + */ + public int getTitleResId() { + return 0; + } + + int getTabId(Activity activity, Bundle bundle) { + if (bundle != null) { + final int extraTab = bundle.getInt(EXTRA_SHOW_FRAGMENT_TAB, -1); + if (extraTab != -1) { + return extraTab; + } + } + return PERSONAL_TAB; + } + + private CharSequence getPageTitle(int position) { + if (position == WORK_TAB) { + return getContext().getString(R.string.settingslib_category_work); + } + + return getString(R.string.settingslib_category_personal); + } +} diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java new file mode 100644 index 000000000000..daf2564a674e --- /dev/null +++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.widget; + +import androidx.fragment.app.Fragment; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +/** + * ViewPager Adapter to handle between TabLayout and ViewPager2 + */ +public class ProfileViewPagerAdapter extends FragmentStateAdapter { + + private final ProfileSelectFragment mParentFragments; + + ProfileViewPagerAdapter(ProfileSelectFragment fragment) { + super(fragment); + mParentFragments = fragment; + } + + @Override + public Fragment createFragment(int position) { + return mParentFragments.createFragment(position); + } + + @Override + public int getItemCount() { + return 2; + } +} diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp index bcc64d3cd234..b5a21bdae606 100644 --- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp +++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp @@ -23,5 +23,6 @@ android_library { apex_available: [ "//apex_available:platform", "com.android.permission", + "com.android.mediaprovider", ], } diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp index 82e0220997d3..939977fa7ef2 100644 --- a/packages/SettingsLib/SettingsTheme/Android.bp +++ b/packages/SettingsLib/SettingsTheme/Android.bp @@ -24,5 +24,6 @@ android_library { "com.android.permission", "com.android.adservices", "com.android.healthconnect", + "com.android.mediaprovider", ], } diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle index 20dd707de482..f0b6ac540c49 100644 --- a/packages/SettingsLib/Spa/gallery/build.gradle +++ b/packages/SettingsLib/Spa/gallery/build.gradle @@ -58,4 +58,5 @@ android { dependencies { implementation(project(":spa")) + implementation "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha" } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt index 36b58ad794dc..dfbf244696bb 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt @@ -22,6 +22,6 @@ import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory class GalleryApplication : Application() { override fun onCreate() { super.onCreate() - SpaEnvironmentFactory.reset(GallerySpaEnvironment) + SpaEnvironmentFactory.reset(GallerySpaEnvironment(this)) } }
\ No newline at end of file diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 4af25893ea37..742e2712e41a 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -16,6 +16,7 @@ package com.android.settingslib.spa.gallery +import android.content.Context import com.android.settingslib.spa.framework.common.LocalLogger import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository import com.android.settingslib.spa.framework.common.SpaEnvironment @@ -23,6 +24,7 @@ import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider import com.android.settingslib.spa.gallery.home.HomePageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageProvider +import com.android.settingslib.spa.gallery.page.ChartPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider @@ -49,7 +51,7 @@ enum class SettingsPageProviderEnum(val displayName: String) { // Add your SPPs } -object GallerySpaEnvironment : SpaEnvironment() { +class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { override val pageProviderRepository = lazy { SettingsPageProviderRepository( allPageProviders = listOf( @@ -68,6 +70,7 @@ object GallerySpaEnvironment : SpaEnvironment() { CategoryPageProvider, ActionButtonPageProvider, ProgressBarPageProvider, + ChartPageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index 7fd49db93748..83c72c75e9f3 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -18,10 +18,10 @@ package com.android.settingslib.spa.gallery.home import android.os.Bundle import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R @@ -29,6 +29,7 @@ import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider import com.android.settingslib.spa.gallery.page.ArgumentPageModel import com.android.settingslib.spa.gallery.page.ArgumentPageProvider +import com.android.settingslib.spa.gallery.page.ChartPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider @@ -56,12 +57,17 @@ object HomePageProvider : SettingsPageProvider { CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ) } + override fun getTitle(arguments: Bundle?): String { + return SpaEnvironmentFactory.instance.appContext.getString(R.string.app_name) + } + @Composable override fun Page(arguments: Bundle?) { - HomeScaffold(title = stringResource(R.string.app_name)) { + HomeScaffold(title = getTitle(arguments)) { for (entry in buildEntry(arguments)) { if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) { entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0)) @@ -76,6 +82,7 @@ object HomePageProvider : SettingsPageProvider { @Preview(showBackground = true) @Composable private fun HomeScreenPreview() { + SpaEnvironmentFactory.resetForPreview() SettingsTheme { HomePageProvider.Page(null) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt index 82073104a3a7..7958d11ad513 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt @@ -23,6 +23,7 @@ import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.SettingsPageProviderEnum @@ -98,9 +99,13 @@ object ArgumentPageProvider : SettingsPageProvider { } } + override fun getTitle(arguments: Bundle?): String { + return ArgumentPageModel.genPageTitle() + } + @Composable override fun Page(arguments: Bundle?) { - RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) { + RegularScaffold(title = getTitle(arguments)) { for (entry in buildEntry(arguments)) { if (entry.toPage != null) { entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments)) @@ -115,6 +120,7 @@ object ArgumentPageProvider : SettingsPageProvider { @Preview(showBackground = true) @Composable private fun ArgumentPagePreview() { + SpaEnvironmentFactory.resetForPreview() SettingsTheme { ArgumentPageProvider.Page( ArgumentPageModel.buildArgument(stringParam = "foo", intParam = 0) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt index e5e3c679a76a..5f15865ff7b9 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt @@ -81,6 +81,10 @@ class ArgumentPageModel : PageModel() { return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS) } + fun genPageTitle(): String { + return PAGE_TITLE + } + @Composable fun create(arguments: Bundle?): ArgumentPageModel { val pageModel: ArgumentPageModel = viewModel(key = arguments.toString()) @@ -89,7 +93,6 @@ class ArgumentPageModel : PageModel() { } } - private val title = PAGE_TITLE private var arguments: Bundle? = null private var stringParam: String? = null private var intParam: Int? = null @@ -104,11 +107,6 @@ class ArgumentPageModel : PageModel() { } @Composable - fun genPageTitle(): String { - return title - } - - @Composable fun genStringParamPreferenceModel(): PreferenceModel { return object : PreferenceModel { override val title = STRING_PARAM_TITLE @@ -131,7 +129,7 @@ class ArgumentPageModel : PageModel() { "$INT_PARAM_NAME=" + intParam!! ) return object : PreferenceModel { - override val title = genPageTitle() + override val title = PAGE_TITLE override val summary = stateOf(summaryArray.joinToString(", ")) override val onClick = navigator( SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt new file mode 100644 index 000000000000..160e77b061eb --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt @@ -0,0 +1,162 @@ +/* + * 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.gallery.page + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPage +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.chart.BarChart +import com.android.settingslib.spa.widget.chart.BarChartData +import com.android.settingslib.spa.widget.chart.BarChartModel +import com.android.settingslib.spa.widget.chart.LineChart +import com.android.settingslib.spa.widget.chart.LineChartData +import com.android.settingslib.spa.widget.chart.LineChartModel +import com.android.settingslib.spa.widget.chart.PieChart +import com.android.settingslib.spa.widget.chart.PieChartData +import com.android.settingslib.spa.widget.chart.PieChartModel +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import java.text.NumberFormat + +private enum class WeekDay(val num: Int) { + Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6), +} +private const val TITLE = "Sample Chart" + +object ChartPageProvider : SettingsPageProvider { + override val name = "Chart" + + override fun buildEntry(arguments: Bundle?): List<SettingsEntry> { + val owner = SettingsPage.create(name) + val entryList = mutableListOf<SettingsEntry>() + entryList.add( + SettingsEntryBuilder.create("Line Chart", owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = "Line Chart" + }) + LineChart( + lineChartModel = object : LineChartModel { + override val chartDataList = listOf( + LineChartData(x = 0f, y = 0f), + LineChartData(x = 1f, y = 0.1f), + LineChartData(x = 2f, y = 0.2f), + LineChartData(x = 3f, y = 0.6f), + LineChartData(x = 4f, y = 0.9f), + LineChartData(x = 5f, y = 1.0f), + LineChartData(x = 6f, y = 0.8f), + ) + override val xValueFormatter = + IAxisValueFormatter { value, _ -> + "${WeekDay.values()[value.toInt()]}" + } + override val yValueFormatter = + IAxisValueFormatter { value, _ -> + NumberFormat.getPercentInstance().format(value) + } + } + ) + }.build() + ) + entryList.add( + SettingsEntryBuilder.create("Bar Chart", owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = "Bar Chart" + }) + BarChart( + barChartModel = object : BarChartModel { + override val chartDataList = listOf( + BarChartData(x = 0f, y = 12f), + BarChartData(x = 1f, y = 5f), + BarChartData(x = 2f, y = 21f), + BarChartData(x = 3f, y = 5f), + BarChartData(x = 4f, y = 10f), + BarChartData(x = 5f, y = 9f), + BarChartData(x = 6f, y = 1f), + ) + override val xValueFormatter = + IAxisValueFormatter { value, _ -> + "${WeekDay.values()[value.toInt()]}" + } + override val yValueFormatter = + IAxisValueFormatter { value, _ -> + "${value.toInt()}m" + } + override val yAxisMaxValue = 30f + } + ) + }.build() + ) + entryList.add( + SettingsEntryBuilder.create("Pie Chart", owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = "Pie Chart" + }) + PieChart( + pieChartModel = object : PieChartModel { + override val chartDataList = listOf( + PieChartData(label = "Settings", value = 20f), + PieChartData(label = "Chrome", value = 5f), + PieChartData(label = "Gmail", value = 3f), + ) + override val centerText = "Today" + } + ) + }.build() + ) + + return entryList + } + + fun buildInjectEntry(): SettingsEntryBuilder { + return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name)) + .setIsAllowSearch(true) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + } + + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = TITLE) { + for (entry in buildEntry(arguments)) { + entry.UiLayout(arguments) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun ChartPagePreview() { + SettingsTheme { + ChartPageProvider.Page(null) + } +} diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt index 0fc2a5f5fda8..c903cfd96ce3 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt @@ -67,9 +67,13 @@ object FooterPageProvider : SettingsPageProvider { } } + override fun getTitle(arguments: Bundle?): String { + return TITLE + } + @Composable override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { + RegularScaffold(title = getTitle(arguments)) { for (entry in buildEntry(arguments)) { entry.UiLayout() } 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 a64d4a5d3ea6..e10cf3aa0702 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 @@ -31,7 +31,6 @@ import com.android.settingslib.spa.widget.IllustrationModel import com.android.settingslib.spa.widget.ResourceType import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import com.android.settingslib.spa.widget.scaffold.RegularScaffold private const val TITLE = "Sample Illustration" @@ -82,13 +81,8 @@ object IllustrationPageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return TITLE } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt index dc45df4a0374..9136b0430c40 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt @@ -60,6 +60,10 @@ object ProgressBarPageProvider : SettingsPageProvider { } } + override fun getTitle(arguments: Bundle?): String { + return TITLE + } + @Composable override fun Page(arguments: Bundle?) { // Mocks a loading time of 2 seconds. @@ -69,7 +73,7 @@ object ProgressBarPageProvider : SettingsPageProvider { loading = false } - RegularScaffold(title = TITLE) { + RegularScaffold(title = getTitle(arguments)) { // Auto update the progress and finally jump tp 0.4f. var progress by remember { mutableStateOf(0f) } LaunchedEffect(Unit) { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt index b38178b0e6f4..cb58a95e01a6 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt @@ -49,9 +49,13 @@ object SettingsPagerPageProvider : SettingsPageProvider { } } + override fun getTitle(arguments: Bundle?): String { + return TITLE + } + @Composable override fun Page(arguments: Bundle?) { - SettingsScaffold(title = TITLE) { paddingValues -> + SettingsScaffold(title = getTitle(arguments)) { paddingValues -> Box(Modifier.padding(paddingValues)) { SettingsPager(listOf("Personal", "Work")) { PlaceholderTitle("Page $it") diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt index 7567c6daf996..73b34a50a520 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt @@ -37,7 +37,6 @@ import com.android.settingslib.spa.widget.preference.SliderPreference import com.android.settingslib.spa.widget.preference.SliderPreferenceModel import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import com.android.settingslib.spa.widget.scaffold.RegularScaffold private const val TITLE = "Sample Slider" @@ -119,13 +118,8 @@ object SliderPageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return TITLE } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt index a8e49384da97..f38a8d4193b3 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt @@ -33,7 +33,6 @@ import com.android.settingslib.spa.widget.preference.MainSwitchPreference import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spa.widget.scaffold.RegularScaffold private const val TITLE = "Sample MainSwitchPreference" @@ -72,13 +71,8 @@ object MainSwitchPreferencePageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return TITLE } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt index 165eaa05c9d5..61925a7b7164 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt @@ -17,7 +17,6 @@ package com.android.settingslib.spa.gallery.preference import android.os.Bundle -import androidx.compose.runtime.Composable import com.android.settingslib.spa.framework.common.SettingsEntry import com.android.settingslib.spa.framework.common.SettingsEntryBuilder import com.android.settingslib.spa.framework.common.SettingsPageProvider @@ -25,7 +24,6 @@ import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.framework.compose.navigator import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel -import com.android.settingslib.spa.widget.scaffold.RegularScaffold private const val TITLE = "Category: Preference" @@ -54,12 +52,7 @@ object PreferenceMainPageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return TITLE } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt index fa8d51c3561f..26e59ff699a4 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt @@ -49,7 +49,6 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Compan import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro -import com.android.settingslib.spa.widget.scaffold.RegularScaffold import com.android.settingslib.spa.widget.ui.SettingsIcon private const val TAG = "PreferencePage" @@ -128,11 +127,11 @@ object PreferencePageProvider : SettingsPageProvider { .setStatusDataFn { EntryStatusData(isDisabled = false) } .setUiLayoutFn { val model = PreferencePageModel.create() - val asyncSummary = remember { model.getAsyncSummary() } Preference( object : PreferenceModel { override val title = ASYNC_PREFERENCE_TITLE - override val summary = asyncSummary + override val summary = model.asyncSummary + override val enabled = model.asyncEnable } ) }.build() @@ -204,19 +203,15 @@ object PreferencePageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = PAGE_TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return PAGE_TITLE } } @Preview(showBackground = true) @Composable private fun PreferencePagePreview() { + SpaEnvironmentFactory.resetForPreview() SettingsTheme { PreferencePageProvider.Page(null) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt index 1e64b2edb60b..d874417c1a56 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt @@ -59,7 +59,8 @@ class PreferencePageModel : PageModel() { private val spaLogger = SpaEnvironmentFactory.instance.logger - private val asyncSummary = mutableStateOf(" ") + val asyncSummary = mutableStateOf("(loading)") + val asyncEnable = mutableStateOf(false) private val manualUpdater = mutableStateOf(0) @@ -87,16 +88,13 @@ class PreferencePageModel : PageModel() { override fun initialize(arguments: Bundle?) { spaLogger.message(TAG, "initialize with args " + arguments.toString()) viewModelScope.launch(Dispatchers.IO) { + // Loading your data here. delay(2000L) asyncSummary.value = ASYNC_PREFERENCE_SUMMARY + asyncEnable.value = true } } - fun getAsyncSummary(): State<String> { - spaLogger.message(TAG, "getAsyncSummary") - return asyncSummary - } - fun getManualUpdaterSummary(): State<String> { spaLogger.message(TAG, "getManualUpdaterSummary") return derivedStateOf { manualUpdater.value.toString() } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt index 46b44ca9d614..367766a34a3c 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt @@ -34,7 +34,6 @@ import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreference import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel -import com.android.settingslib.spa.widget.scaffold.RegularScaffold import kotlinx.coroutines.delay private const val TITLE = "Sample SwitchPreference" @@ -88,13 +87,8 @@ object SwitchPreferencePageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return TITLE } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt index b991f59866eb..22da99c23fc8 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt @@ -34,7 +34,6 @@ import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference -import com.android.settingslib.spa.widget.scaffold.RegularScaffold import kotlinx.coroutines.delay private const val TITLE = "Sample TwoTargetSwitchPreference" @@ -88,13 +87,8 @@ object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { - for (entry in buildEntry(arguments)) { - entry.UiLayout() - } - } + override fun getTitle(arguments: Bundle?): String { + return TITLE } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt index a4713b993af0..d87cbe82d9d8 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt @@ -48,41 +48,40 @@ object CategoryPageProvider : SettingsPageProvider { } } - @Composable - override fun Page(arguments: Bundle?) { - CategoryPage() + override fun getTitle(arguments: Bundle?): String { + return TITLE } -} -@Composable -private fun CategoryPage() { - RegularScaffold(title = TITLE) { - CategoryTitle("Category A") - Preference(remember { - object : PreferenceModel { - override val title = "Preference 1" - override val summary = stateOf("Summary 1") - } - }) - Preference(remember { - object : PreferenceModel { - override val title = "Preference 2" - override val summary = stateOf("Summary 2") - } - }) - Category("Category B") { + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = getTitle(arguments)) { + CategoryTitle("Category A") Preference(remember { object : PreferenceModel { - override val title = "Preference 3" - override val summary = stateOf("Summary 3") + override val title = "Preference 1" + override val summary = stateOf("Summary 1") } }) Preference(remember { object : PreferenceModel { - override val title = "Preference 4" - override val summary = stateOf("Summary 4") + override val title = "Preference 2" + override val summary = stateOf("Summary 2") } }) + Category("Category B") { + Preference(remember { + object : PreferenceModel { + override val title = "Preference 3" + override val summary = stateOf("Summary 3") + } + }) + Preference(remember { + object : PreferenceModel { + override val title = "Preference 4" + override val summary = stateOf("Summary 4") + } + }) + } } } } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt index 03b72d348d40..ec2f436c9e98 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt @@ -49,9 +49,13 @@ object SpinnerPageProvider : SettingsPageProvider { } } + override fun getTitle(arguments: Bundle?): String { + return TITLE + } + @Composable override fun Page(arguments: Bundle?) { - RegularScaffold(title = TITLE) { + RegularScaffold(title = getTitle(arguments)) { val selectedIndex = rememberSaveable { mutableStateOf(0) } Spinner( options = (1..3).map { "Option $it" }, diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle index 897fa675888d..cef79c16b38a 100644 --- a/packages/SettingsLib/Spa/settings.gradle +++ b/packages/SettingsLib/Spa/settings.gradle @@ -26,6 +26,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven { url "https://jitpack.io"} } } rootProject.name = "SpaLib" diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp index 8b29366ef6a3..037b45e6732a 100644 --- a/packages/SettingsLib/Spa/spa/Android.bp +++ b/packages/SettingsLib/Spa/spa/Android.bp @@ -33,6 +33,7 @@ android_library { "androidx.navigation_navigation-compose", "com.google.android.material_material", "lottie_compose", + "MPAndroidChart", ], kotlincflags: [ "-Xjvm-default=all", diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle index 3b159e95cc55..682d3fc055cc 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle +++ b/packages/SettingsLib/Spa/spa/build.gradle @@ -64,4 +64,5 @@ dependencies { 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" + implementation "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha" } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt index f8963b2a8837..151b50cdb5c4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.framework.common import android.os.Bundle import androidx.compose.runtime.Composable import androidx.navigation.NamedNavArgument +import com.android.settingslib.spa.widget.scaffold.RegularScaffold /** * An SettingsPageProvider which is used to create Settings page instances. @@ -36,13 +37,19 @@ interface SettingsPageProvider { val parameter: List<NamedNavArgument> get() = emptyList() - /** The [Composable] used to render this page. */ - @Composable - fun Page(arguments: Bundle?) + fun getTitle(arguments: Bundle?): String = displayName ?: name fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList() - fun getTitle(arguments: Bundle?): String = displayName ?: name + /** The [Composable] used to render this page. */ + @Composable + fun Page(arguments: Bundle?) { + RegularScaffold(title = getTitle(arguments)) { + for (entry in buildEntry(arguments)) { + entry.UiLayout() + } + } + } } fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt index 5baee4fb4acc..b83104360260 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt @@ -17,7 +17,10 @@ package com.android.settingslib.spa.framework.common import android.app.Activity +import android.content.Context import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext private const val TAG = "SpaEnvironment" @@ -29,6 +32,20 @@ object SpaEnvironmentFactory { Log.d(TAG, "reset") } + @Composable + fun resetForPreview() { + val context = LocalContext.current + spaEnvironment = object : SpaEnvironment(context) { + override val pageProviderRepository = lazy { + SettingsPageProviderRepository( + allPageProviders = emptyList(), + rootPages = emptyList() + ) + } + } + Log.d(TAG, "resetForPreview") + } + val instance: SpaEnvironment get() { if (spaEnvironment == null) @@ -37,11 +54,14 @@ object SpaEnvironmentFactory { } } -abstract class SpaEnvironment { +abstract class SpaEnvironment(context: Context) { abstract val pageProviderRepository: Lazy<SettingsPageProviderRepository> val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) } + // In Robolectric test, applicationContext is not available. Use context as fallback. + val appContext: Context = context.applicationContext ?: context + open val browseActivityClass: Class<out Activity>? = null open val entryProviderAuthorities: String? = null diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt index 8e8805aa49d2..94792289c785 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt @@ -21,45 +21,51 @@ package com.android.settingslib.spa.framework.theme import android.annotation.SuppressLint import android.content.Context import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.ExperimentalTextApi import androidx.compose.ui.text.font.DeviceFontFamilyName import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight +import com.android.settingslib.spa.framework.compose.rememberContext internal data class SettingsFontFamily( - val brand: FontFamily = FontFamily.Default, - val plain: FontFamily = FontFamily.Default, + val brand: FontFamily, + val plain: FontFamily, ) -private fun Context.getSettingsFontFamily(inInspection: Boolean): SettingsFontFamily { - if (inInspection) { - return SettingsFontFamily() - } +private fun Context.getSettingsFontFamily(): SettingsFontFamily { return SettingsFontFamily( - brand = FontFamily( - Font(getFontFamilyName("config_headlineFontFamily"), FontWeight.Normal), - Font(getFontFamilyName("config_headlineFontFamilyMedium"), FontWeight.Medium), + brand = getFontFamily( + configFontFamilyNormal = "config_headlineFontFamily", + configFontFamilyMedium = "config_headlineFontFamilyMedium", ), - plain = FontFamily( - Font(getFontFamilyName("config_bodyFontFamily"), FontWeight.Normal), - Font(getFontFamilyName("config_bodyFontFamilyMedium"), FontWeight.Medium), + plain = getFontFamily( + configFontFamilyNormal = "config_bodyFontFamily", + configFontFamilyMedium = "config_bodyFontFamilyMedium", ), ) } -private fun Context.getFontFamilyName(configName: String): DeviceFontFamilyName { +private fun Context.getFontFamily( + configFontFamilyNormal: String, + configFontFamilyMedium: String, +): FontFamily { + val fontFamilyNormal = getAndroidConfig(configFontFamilyNormal) + val fontFamilyMedium = getAndroidConfig(configFontFamilyMedium) + if (fontFamilyNormal.isEmpty() || fontFamilyMedium.isEmpty()) return FontFamily.Default + return FontFamily( + Font(DeviceFontFamilyName(fontFamilyNormal), FontWeight.Normal), + Font(DeviceFontFamilyName(fontFamilyMedium), FontWeight.Medium), + ) +} + +private fun Context.getAndroidConfig(configName: String): String { @SuppressLint("DiscouragedApi") val configId = resources.getIdentifier(configName, "string", "android") - return DeviceFontFamilyName(resources.getString(configId)) + return resources.getString(configId) } @Composable internal fun rememberSettingsFontFamily(): SettingsFontFamily { - val context = LocalContext.current - val inInspection = LocalInspectionMode.current - return remember { context.getSettingsFontFamily(inInspection) } + return rememberContext(Context::getSettingsFontFamily) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt new file mode 100644 index 000000000000..0b0f07e670ee --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt @@ -0,0 +1,202 @@ +/* + * 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.chart + +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.android.settingslib.spa.framework.theme.divider +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.formatter.IAxisValueFormatter + +/** + * The chart settings model for [BarChart]. + */ +interface BarChartModel { + /** + * The chart data list for [BarChart]. + */ + val chartDataList: List<BarChartData> + + /** + * The label text formatter for x value. + */ + val xValueFormatter: IAxisValueFormatter? + get() = null + + /** + * The label text formatter for y value. + */ + val yValueFormatter: IAxisValueFormatter? + get() = null + + /** + * The minimum value for y-axis. + */ + val yAxisMinValue: Float + get() = 0f + + /** + * The maximum value for y-axis. + */ + val yAxisMaxValue: Float + get() = 1f + + /** + * The label count for y-axis. + */ + val yAxisLabelCount: Int + get() = 3 +} + +data class BarChartData( + var x: Float?, + var y: Float?, +) + +@Composable +fun BarChart(barChartModel: BarChartModel) { + BarChart( + chartDataList = barChartModel.chartDataList, + xValueFormatter = barChartModel.xValueFormatter, + yValueFormatter = barChartModel.yValueFormatter, + yAxisMinValue = barChartModel.yAxisMinValue, + yAxisMaxValue = barChartModel.yAxisMaxValue, + yAxisLabelCount = barChartModel.yAxisLabelCount, + ) +} + +@Composable +fun BarChart( + chartDataList: List<BarChartData>, + modifier: Modifier = Modifier, + xValueFormatter: IAxisValueFormatter? = null, + yValueFormatter: IAxisValueFormatter? = null, + yAxisMinValue: Float = 0f, + yAxisMaxValue: Float = 30f, + yAxisLabelCount: Int = 4, +) { + Column( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Column( + modifier = Modifier + .padding(16.dp) + .height(170.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + val colorScheme = MaterialTheme.colorScheme + val labelTextColor = colorScheme.onSurfaceVariant.toArgb() + val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value + Crossfade(targetState = chartDataList) { barChartData -> + AndroidView(factory = { context -> + BarChart(context).apply { + // Fixed Settings. + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + this.description.isEnabled = false + this.legend.isEnabled = false + this.extraBottomOffset = 4f + this.setScaleEnabled(false) + + this.xAxis.position = XAxis.XAxisPosition.BOTTOM + this.xAxis.setDrawGridLines(false) + this.xAxis.setDrawAxisLine(false) + this.xAxis.textColor = labelTextColor + this.xAxis.textSize = labelTextSize + this.xAxis.yOffset = 10f + + this.axisLeft.isEnabled = false + this.axisRight.setDrawAxisLine(false) + this.axisRight.textSize = labelTextSize + this.axisRight.textColor = labelTextColor + this.axisRight.gridColor = colorScheme.divider.toArgb() + this.axisRight.xOffset = 10f + + // Customizable Settings. + this.xAxis.valueFormatter = xValueFormatter + this.axisRight.valueFormatter = yValueFormatter + + this.axisLeft.axisMinimum = yAxisMinValue + this.axisLeft.axisMaximum = yAxisMaxValue + this.axisRight.axisMinimum = yAxisMinValue + this.axisRight.axisMaximum = yAxisMaxValue + + this.axisRight.setLabelCount(yAxisLabelCount, true) + } + }, + modifier = Modifier + .wrapContentSize() + .padding(4.dp), + update = { + updateBarChartWithData(it, barChartData, colorScheme) + } + ) + } + } + } +} + +fun updateBarChartWithData( + chart: BarChart, + data: List<BarChartData>, + colorScheme: ColorScheme +) { + val entries = ArrayList<BarEntry>() + for (i in data.indices) { + val item = data[i] + entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat())) + } + + val ds = BarDataSet(entries, "") + ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb()) + ds.setDrawValues(false) + ds.isHighlightEnabled = true + ds.highLightColor = colorScheme.primary.toArgb() + ds.highLightAlpha = 255 + // TODO: Sets round corners for bars. + + val d = BarData(ds) + chart.data = d + chart.invalidate() +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt new file mode 100644 index 000000000000..70bc017c6c5b --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt @@ -0,0 +1,29 @@ +/* + * 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.chart + +import androidx.compose.ui.graphics.Color + +object ColorPalette { + // Alpha = 1 + val red: Color = Color(0xffd93025) + val orange: Color = Color(0xffe8710a) + val yellow: Color = Color(0xfff9ab00) + val green: Color = Color(0xff1e8e3e) + val cyan: Color = Color(0xff12b5cb) + val blue: Color = Color(0xff1a73e8) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt new file mode 100644 index 000000000000..7d482656d87c --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt @@ -0,0 +1,217 @@ +/* + * 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.chart + +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.android.settingslib.spa.framework.theme.divider +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.formatter.IAxisValueFormatter + +/** + * The chart settings model for [LineChart]. + */ +interface LineChartModel { + /** + * The chart data list for [LineChart]. + */ + val chartDataList: List<LineChartData> + + /** + * The label text formatter for x value. + */ + val xValueFormatter: IAxisValueFormatter? + get() = null + + /** + * The label text formatter for y value. + */ + val yValueFormatter: IAxisValueFormatter? + get() = null + + /** + * The minimum value for y-axis. + */ + val yAxisMinValue: Float + get() = 0f + + /** + * The maximum value for y-axis. + */ + val yAxisMaxValue: Float + get() = 1f + + /** + * The label count for y-axis. + */ + val yAxisLabelCount: Int + get() = 3 + + /** + * Indicates whether to smooth the line. + */ + val showSmoothLine: Boolean + get() = true +} + +data class LineChartData( + var x: Float?, + var y: Float?, +) + +@Composable +fun LineChart(lineChartModel: LineChartModel) { + LineChart( + chartDataList = lineChartModel.chartDataList, + xValueFormatter = lineChartModel.xValueFormatter, + yValueFormatter = lineChartModel.yValueFormatter, + yAxisMinValue = lineChartModel.yAxisMinValue, + yAxisMaxValue = lineChartModel.yAxisMaxValue, + yAxisLabelCount = lineChartModel.yAxisLabelCount, + showSmoothLine = lineChartModel.showSmoothLine, + ) +} + +@Composable +fun LineChart( + chartDataList: List<LineChartData>, + modifier: Modifier = Modifier, + xValueFormatter: IAxisValueFormatter? = null, + yValueFormatter: IAxisValueFormatter? = null, + yAxisMinValue: Float = 0f, + yAxisMaxValue: Float = 1f, + yAxisLabelCount: Int = 3, + showSmoothLine: Boolean = true, +) { + Column( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Column( + modifier = Modifier + .padding(16.dp) + .height(170.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + val colorScheme = MaterialTheme.colorScheme + val labelTextColor = colorScheme.onSurfaceVariant.toArgb() + val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value + Crossfade(targetState = chartDataList) { lineChartData -> + AndroidView(factory = { context -> + LineChart(context).apply { + // Fixed Settings. + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + this.description.isEnabled = false + this.legend.isEnabled = false + this.extraBottomOffset = 4f + this.setTouchEnabled(false) + + this.xAxis.position = XAxis.XAxisPosition.BOTTOM + this.xAxis.setDrawGridLines(false) + this.xAxis.setDrawAxisLine(false) + this.xAxis.textColor = labelTextColor + this.xAxis.textSize = labelTextSize + this.xAxis.yOffset = 10f + + this.axisLeft.isEnabled = false + + this.axisRight.setDrawAxisLine(false) + this.axisRight.textSize = labelTextSize + this.axisRight.textColor = labelTextColor + this.axisRight.gridColor = colorScheme.divider.toArgb() + this.axisRight.xOffset = 10f + this.axisRight.isGranularityEnabled = true + + // Customizable Settings. + this.xAxis.valueFormatter = xValueFormatter + this.axisRight.valueFormatter = yValueFormatter + + this.axisLeft.axisMinimum = + yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue) + this.axisRight.axisMinimum = + yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue) + this.axisRight.granularity = + (yAxisMaxValue - yAxisMinValue) / (yAxisLabelCount - 1) + } + }, + modifier = Modifier + .wrapContentSize() + .padding(4.dp), + update = { + updateLineChartWithData(it, lineChartData, colorScheme, showSmoothLine) + }) + } + } + } +} + +fun updateLineChartWithData( + chart: LineChart, + data: List<LineChartData>, + colorScheme: ColorScheme, + showSmoothLine: Boolean +) { + val entries = ArrayList<Entry>() + for (i in data.indices) { + val item = data[i] + entries.add(Entry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat())) + } + + val ds = LineDataSet(entries, "") + ds.colors = arrayListOf(colorScheme.primary.toArgb()) + ds.lineWidth = 2f + if (showSmoothLine) { + ds.mode = LineDataSet.Mode.CUBIC_BEZIER + } + ds.setDrawValues(false) + ds.setDrawCircles(false) + ds.setDrawFilled(true) + ds.fillColor = colorScheme.primary.toArgb() + ds.fillAlpha = 38 + // TODO: enable gradient fill color for line chart. + + val d = LineData(ds) + chart.data = d + chart.invalidate() +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt new file mode 100644 index 000000000000..51a8d0d5ff2b --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt @@ -0,0 +1,163 @@ +/* + * 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.chart + +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry + +/** + * The chart settings model for [PieChart]. + */ +interface PieChartModel { + /** + * The chart data list for [PieChart]. + */ + val chartDataList: List<PieChartData> + + /** + * The center text in the hole of [PieChart]. + */ + val centerText: String? + get() = null +} + +val colorPalette = arrayListOf( + ColorPalette.blue.toArgb(), + ColorPalette.red.toArgb(), + ColorPalette.yellow.toArgb(), + ColorPalette.green.toArgb(), + ColorPalette.orange.toArgb(), + ColorPalette.cyan.toArgb(), + Color.Blue.toArgb() +) + +data class PieChartData( + var value: Float?, + var label: String?, +) + +@Composable +fun PieChart(pieChartModel: PieChartModel) { + PieChart( + chartDataList = pieChartModel.chartDataList, + centerText = pieChartModel.centerText, + ) +} + +@Composable +fun PieChart( + chartDataList: List<PieChartData>, + modifier: Modifier = Modifier, + centerText: String? = null, +) { + Column( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Column( + modifier = Modifier + .padding(16.dp) + .height(280.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + val colorScheme = MaterialTheme.colorScheme + val labelTextColor = colorScheme.onSurfaceVariant.toArgb() + val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value + val centerTextSize = MaterialTheme.typography.titleLarge.fontSize.value + Crossfade(targetState = chartDataList) { pieChartData -> + AndroidView(factory = { context -> + PieChart(context).apply { + // Fixed settings.` + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + this.isRotationEnabled = false + this.description.isEnabled = false + this.legend.isEnabled = false + this.setTouchEnabled(false) + + this.isDrawHoleEnabled = true + this.holeRadius = 90.0f + this.setHoleColor(Color.Transparent.toArgb()) + this.setEntryLabelColor(labelTextColor) + this.setEntryLabelTextSize(labelTextSize) + this.setCenterTextSize(centerTextSize) + this.setCenterTextColor(colorScheme.onSurface.toArgb()) + + // Customizable settings. + this.centerText = centerText + } + }, + modifier = Modifier + .wrapContentSize() + .padding(4.dp), update = { + updatePieChartWithData(it, pieChartData) + }) + } + } + } +} + +fun updatePieChartWithData( + chart: PieChart, + data: List<PieChartData>, +) { + val entries = ArrayList<PieEntry>() + for (i in data.indices) { + val item = data[i] + entries.add(PieEntry(item.value ?: 0.toFloat(), item.label ?: "")) + } + + val ds = PieDataSet(entries, "") + ds.setDrawValues(false) + ds.colors = colorPalette + ds.sliceSpace = 2f + ds.yValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE + ds.xValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE + ds.valueLineColor = Color.Transparent.toArgb() + ds.valueLinePart1Length = 0.1f + ds.valueLinePart2Length = 0f + + val d = PieData(ds) + chart.data = d + chart.invalidate() +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt index d09aec9460dd..e26bdf76e9d6 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt @@ -36,14 +36,15 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme @Composable internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) { val entryData = LocalEntryDataProvider.current - var isHighlighted by rememberSaveable { mutableStateOf(false) } + val entryIsHighlighted = rememberSaveable { entryData.isHighlighted } + var localHighlighted by rememberSaveable { mutableStateOf(false) } SideEffect { - isHighlighted = entryData.isHighlighted + localHighlighted = entryIsHighlighted } val backgroundColor by animateColorAsState( targetValue = when { - isHighlighted -> MaterialTheme.colorScheme.surfaceVariant + localHighlighted -> MaterialTheme.colorScheme.surfaceVariant else -> SettingsTheme.colorScheme.background }, animationSpec = repeatable( diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp index 74910456471e..2449dec36d26 100644 --- a/packages/SettingsLib/Spa/tests/Android.bp +++ b/packages/SettingsLib/Spa/tests/Android.bp @@ -31,6 +31,7 @@ android_test { "androidx.compose.runtime_runtime", "androidx.compose.ui_ui-test-junit4", "androidx.compose.ui_ui-test-manifest", + "mockito-target-minus-junit4", "truth-prebuilt", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle index 4b4c6a35e92d..52610919699b 100644 --- a/packages/SettingsLib/Spa/tests/build.gradle +++ b/packages/SettingsLib/Spa/tests/build.gradle @@ -58,9 +58,10 @@ android { } dependencies { - androidTestImplementation(project(":spa")) - androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3' - androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version") - androidTestImplementation 'com.google.truth:truth:1.1.3' + androidTestImplementation project(":spa") + androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3" + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version" + androidTestImplementation "com.google.truth:truth:1.1.3" + androidTestImplementation "org.mockito:mockito-android:3.4.6" androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version" } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt new file mode 100644 index 000000000000..2ff3039eb197 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.framework.theme + +import android.content.Context +import android.content.res.Resources +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Typography +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.text.font.FontFamily +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.anyInt +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class SettingsThemeTest { + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @get:Rule + val composeTestRule = createComposeRule() + + @Mock + private lateinit var context: Context + + @Mock + private lateinit var resources: Resources + + private var nextMockResId = 1 + + @Before + fun setUp() { + whenever(context.resources).thenReturn(resources) + whenever(resources.getString(anyInt())).thenReturn("") + } + + private fun mockAndroidConfig(configName: String, configValue: String) { + whenever(resources.getIdentifier(configName, "string", "android")) + .thenReturn(nextMockResId) + whenever(resources.getString(nextMockResId)).thenReturn(configValue) + nextMockResId++ + } + + @Test + fun noFontFamilyConfig_useDefaultFontFamily() { + val fontFamily = getFontFamily() + + assertThat(fontFamily.headlineLarge.fontFamily).isSameInstanceAs(FontFamily.Default) + assertThat(fontFamily.bodyLarge.fontFamily).isSameInstanceAs(FontFamily.Default) + } + + @Test + fun hasFontFamilyConfig_useConfiguredFontFamily() { + mockAndroidConfig("config_headlineFontFamily", "HeadlineNormal") + mockAndroidConfig("config_headlineFontFamilyMedium", "HeadlineMedium") + mockAndroidConfig("config_bodyFontFamily", "BodyNormal") + mockAndroidConfig("config_bodyFontFamilyMedium", "BodyMedium") + + val fontFamily = getFontFamily() + + val headlineFontFamily = fontFamily.headlineLarge.fontFamily.toString() + assertThat(headlineFontFamily).contains("HeadlineNormal") + assertThat(headlineFontFamily).contains("HeadlineMedium") + val bodyFontFamily = fontFamily.bodyLarge.fontFamily.toString() + assertThat(bodyFontFamily).contains("BodyNormal") + assertThat(bodyFontFamily).contains("BodyMedium") + } + + private fun getFontFamily(): Typography { + lateinit var typography: Typography + composeTestRule.setContent { + CompositionLocalProvider(LocalContext provides context) { + SettingsTheme { + typography = MaterialTheme.typography + } + } + } + return typography + } +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt new file mode 100644 index 000000000000..21ff08515ee6 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt @@ -0,0 +1,182 @@ +/* + * 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.core.os.bundleOf +import androidx.navigation.NamedNavArgument +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ParameterTest { + @Test + fun navRouteTest() { + val navArguments = listOf( + navArgument("string_param") { type = NavType.StringType }, + navArgument("int_param") { type = NavType.IntType }, + ) + + val route = navArguments.navRoute() + assertThat(route).isEqualTo("/{string_param}/{int_param}") + } + + @Test + fun navLinkTest() { + val navArguments = listOf( + navArgument("string_param") { type = NavType.StringType }, + navArgument("int_param") { type = NavType.IntType }, + ) + + val unsetAllLink = navArguments.navLink() + assertThat(unsetAllLink).isEqualTo("/[unset]/[unset]") + + val setAllLink = navArguments.navLink( + bundleOf( + "string_param" to "myStr", + "int_param" to 10, + ) + ) + assertThat(setAllLink).isEqualTo("/myStr/10") + + val setUnknownLink = navArguments.navLink( + bundleOf( + "string_param" to "myStr", + "int_param" to 10, + "unknown_param" to "unknown", + ) + ) + assertThat(setUnknownLink).isEqualTo("/myStr/10") + + val setWrongTypeLink = navArguments.navLink( + bundleOf( + "string_param" to "myStr", + "int_param" to "wrongStr", + ) + ) + assertThat(setWrongTypeLink).isEqualTo("/myStr/0") + } + + @Test + fun normalizeTest() { + val emptyArguments = emptyList<NamedNavArgument>() + assertThat(emptyArguments.normalize()).isNull() + + val navArguments = listOf( + navArgument("string_param") { type = NavType.StringType }, + navArgument("int_param") { type = NavType.IntType }, + navArgument("rt_param") { type = NavType.StringType }, + ) + + val emptyParam = navArguments.normalize() + assertThat(emptyParam).isNotNull() + assertThat(emptyParam.toString()).isEqualTo( + "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]" + ) + + val setParialParam = navArguments.normalize( + bundleOf( + "string_param" to "myStr", + "rt_param" to "rtStr", + ) + ) + assertThat(setParialParam).isNotNull() + assertThat(setParialParam.toString()).isEqualTo( + "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]" + ) + + val setAllParam = navArguments.normalize( + bundleOf( + "string_param" to "myStr", + "int_param" to 10, + "rt_param" to "rtStr", + ) + ) + assertThat(setAllParam).isNotNull() + assertThat(setAllParam.toString()).isEqualTo( + "Bundle[{rt_param=null, int_param=10, string_param=myStr}]" + ) + } + + @Test + fun getArgTest() { + val navArguments = listOf( + navArgument("string_param") { type = NavType.StringType }, + navArgument("int_param") { type = NavType.IntType }, + ) + + assertThat( + navArguments.getStringArg( + "string_param", bundleOf( + "string_param" to "myStr", + ) + ) + ).isEqualTo("myStr") + + assertThat( + navArguments.getStringArg( + "string_param", bundleOf( + "string_param" to 10, + ) + ) + ).isNull() + + assertThat( + navArguments.getStringArg( + "unknown_param", bundleOf( + "string_param" to "myStr", + ) + ) + ).isNull() + + assertThat(navArguments.getStringArg("string_param")).isNull() + + assertThat( + navArguments.getIntArg( + "int_param", bundleOf( + "int_param" to 10, + ) + ) + ).isEqualTo(10) + + assertThat( + navArguments.getIntArg( + "int_param", bundleOf( + "int_param" to "10", + ) + ) + ).isEqualTo(0) + + assertThat( + navArguments.getIntArg( + "unknown_param", bundleOf( + "int_param" to 10, + ) + ) + ).isNull() + + assertThat(navArguments.getIntArg("int_param")).isNull() + } + + @Test + fun isRuntimeParamTest() { + val regularParam = navArgument("regular_param") { type = NavType.StringType } + val rtParam = navArgument("rt_param") { type = NavType.StringType } + assertThat(regularParam.isRuntimeParam()).isFalse() + assertThat(rtParam.isRuntimeParam()).isTrue() + } +} 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/ChartTest.kt new file mode 100644 index 000000000000..fa7a98add2ae --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spa.widget + +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.SemanticsPropertyKey +import androidx.compose.ui.semantics.SemanticsPropertyReceiver +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.test.SemanticsMatcher +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 + +@RunWith(AndroidJUnit4::class) +class ChartTest { + @get:Rule + val composeTestRule = createComposeRule() + + private val Chart = SemanticsPropertyKey<String>("Chart") + private var SemanticsPropertyReceiver.chart by Chart + private fun hasChart(chart: String): SemanticsMatcher = + SemanticsMatcher.expectValue(Chart, chart) + + @Test + fun line_chart_displayed() { + composeTestRule.setContent { + LineChart( + chartDataList = listOf( + LineChartData(x = 0f, y = 0f), + LineChartData(x = 1f, y = 0.1f), + LineChartData(x = 2f, y = 0.2f), + LineChartData(x = 3f, y = 0.7f), + LineChartData(x = 4f, y = 0.9f), + LineChartData(x = 5f, y = 1.0f), + LineChartData(x = 6f, y = 0.8f), + ), + modifier = Modifier.semantics { chart = "line" } + ) + } + + composeTestRule.onNode(hasChart("line")).assertIsDisplayed() + } + + @Test + fun bar_chart_displayed() { + composeTestRule.setContent { + BarChart( + chartDataList = listOf( + BarChartData(x = 0f, y = 12f), + BarChartData(x = 1f, y = 5f), + BarChartData(x = 2f, y = 21f), + BarChartData(x = 3f, y = 5f), + BarChartData(x = 4f, y = 10f), + BarChartData(x = 5f, y = 9f), + BarChartData(x = 6f, y = 1f), + ), + yAxisMaxValue = 30f, + modifier = Modifier.semantics { chart = "bar" } + ) + } + + composeTestRule.onNode(hasChart("bar")).assertIsDisplayed() + } + + @Test + fun pie_chart_displayed() { + composeTestRule.setContent { + PieChart( + chartDataList = listOf( + PieChartData(label = "Settings", value = 20f), + PieChartData(label = "Chrome", value = 5f), + PieChartData(label = "Gmail", value = 3f), + ), + centerText = "Today", + modifier = Modifier.semantics { chart = "pie" } + ) + } + + composeTestRule.onNode(hasChart("pie")).assertIsDisplayed() + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt index fd723dd067d7..bb1cd6e44867 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt @@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager import android.app.usage.StorageStatsManager import android.apphibernation.AppHibernationManager import android.content.Context +import android.content.pm.CrossProfileApps import android.content.pm.verify.domain.DomainVerificationManager import android.os.UserHandle import android.os.UserManager @@ -36,6 +37,9 @@ val Context.appHibernationManager get() = getSystemService(AppHibernationManager /** The [AppOpsManager] instance. */ val Context.appOpsManager get() = getSystemService(AppOpsManager::class.java)!! +/** The [CrossProfileApps] instance. */ +val Context.crossProfileApps get() = getSystemService(CrossProfileApps::class.java)!! + /** The [DevicePolicyManager] instance. */ val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!! diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt new file mode 100644 index 000000000000..2b2f11c7e23f --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.app + +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.ResolveInfoFlags + +/** + * Checks if a package is system module. + */ +fun PackageManager.isSystemModule(packageName: String): Boolean = try { + getModuleInfo(packageName, 0) + true +} catch (_: PackageManager.NameNotFoundException) { + // Expected, not system module + false +} + +/** + * Resolves the activity to start for a given application and action. + */ +fun PackageManager.resolveActionForApp( + app: ApplicationInfo, + action: String, + flags: Int = 0, +): ActivityInfo? { + val intent = Intent(action).apply { + `package` = app.packageName + } + return resolveActivityAsUser(intent, ResolveInfoFlags.of(flags.toLong()), app.userId) + ?.activityInfo +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt new file mode 100644 index 000000000000..4207490cba80 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.app + +import android.content.ComponentName +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.ModuleInfo +import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException +import android.content.pm.PackageManager.ResolveInfoFlags +import android.content.pm.ResolveInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidJUnit4::class) +class PackageManagerExtTest { + @JvmField + @Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Mock + private lateinit var packageManager: PackageManager + + private fun mockResolveActivityAsUser(resolveInfo: ResolveInfo?) { + whenever( + packageManager.resolveActivityAsUser(any(), any<ResolveInfoFlags>(), eq(APP.userId)) + ).thenReturn(resolveInfo) + } + + @Test + fun isSystemModule_whenSystemModule_returnTrue() { + whenever(packageManager.getModuleInfo(PACKAGE_NAME, 0)).thenReturn(ModuleInfo()) + + val isSystemModule = packageManager.isSystemModule(PACKAGE_NAME) + + assertThat(isSystemModule).isTrue() + } + + @Test + fun isSystemModule_whenNotSystemModule_returnFalse() { + whenever(packageManager.getModuleInfo(PACKAGE_NAME, 0)).thenThrow(NameNotFoundException()) + + val isSystemModule = packageManager.isSystemModule(PACKAGE_NAME) + + assertThat(isSystemModule).isFalse() + } + + @Test + fun resolveActionForApp_noResolveInfo() { + mockResolveActivityAsUser(null) + + val activityInfo = packageManager.resolveActionForApp(APP, ACTION) + + assertThat(activityInfo).isNull() + } + + @Test + fun resolveActionForApp_noActivityInfo() { + mockResolveActivityAsUser(ResolveInfo()) + + val activityInfo = packageManager.resolveActionForApp(APP, ACTION) + + assertThat(activityInfo).isNull() + } + + @Test + fun resolveActionForApp_hasActivityInfo() { + mockResolveActivityAsUser(ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = PACKAGE_NAME + name = ACTIVITY_NAME + } + }) + + val activityInfo = packageManager.resolveActionForApp(APP, ACTION)!! + + assertThat(activityInfo.componentName).isEqualTo(ComponentName(PACKAGE_NAME, ACTIVITY_NAME)) + } + + @Test + fun resolveActionForApp_withFlags() { + packageManager.resolveActionForApp( + app = APP, + action = ACTION, + flags = PackageManager.GET_META_DATA, + ) + + val flagsCaptor = ArgumentCaptor.forClass(ResolveInfoFlags::class.java) + verify(packageManager).resolveActivityAsUser(any(), flagsCaptor.capture(), eq(APP.userId)) + assertThat(flagsCaptor.value.value).isEqualTo(PackageManager.GET_META_DATA.toLong()) + } + + private companion object { + const val PACKAGE_NAME = "package.name" + const val ACTIVITY_NAME = "ActivityName" + const val ACTION = "action" + const val UID = 123 + val APP = ApplicationInfo().apply { + packageName = PACKAGE_NAME + uid = UID + } + } +} diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 1738e011287a..efb5eb974afe 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -548,7 +548,7 @@ <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Bu foydalanuvchining umumiy maʼlumotlari topilmadi."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Umumiy maʼlumotlarni yuklashda xatolik yuz berdi. Qayta urining."</string> <string name="blob_id_text" msgid="8680078988996308061">"Umumiy maʼlumotlar identifikatori: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> - <string name="blob_expires_text" msgid="7882727111491739331">"Amal qilish muddati: <xliff:g id="DATE">%s</xliff:g>"</string> + <string name="blob_expires_text" msgid="7882727111491739331">"Muddati: <xliff:g id="DATE">%s</xliff:g>"</string> <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"Umumiy maʼlumotlarni oʻchirishda xatolik yuz berdi."</string> <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"Bu umumiy maʼlumotlar yuzasidan kelgan soʻrov topilmadi. Oʻchirib tashlansinmi?"</string> <string name="accessor_info_title" msgid="8289823651512477787">"Umumiy maʼlumotlar bor ilovalar"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java index c1ee7ad647c6..ca457b04448d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java @@ -20,6 +20,7 @@ import android.content.Context; import android.util.Log; import java.util.List; +import java.util.Objects; import androidx.lifecycle.LiveData; import androidx.room.Database; @@ -39,17 +40,27 @@ public abstract class MobileNetworkDatabase extends RoomDatabase { public abstract MobileNetworkInfoDao mMobileNetworkInfoDao(); + private static MobileNetworkDatabase sInstance; + private static final Object sLOCK = new Object(); + + /** * Create the MobileNetworkDatabase. * * @param context The context. * @return The MobileNetworkDatabase. */ - public static MobileNetworkDatabase createDatabase(Context context) { - return Room.inMemoryDatabaseBuilder(context, MobileNetworkDatabase.class) - .fallbackToDestructiveMigration() - .enableMultiInstanceInvalidation() - .build(); + public static MobileNetworkDatabase getInstance(Context context) { + synchronized (sLOCK) { + if (Objects.isNull(sInstance)) { + Log.d(TAG, "createDatabase."); + sInstance = Room.inMemoryDatabaseBuilder(context, MobileNetworkDatabase.class) + .fallbackToDestructiveMigration() + .enableMultiInstanceInvalidation() + .build(); + } + } + return sInstance; } /** @@ -93,7 +104,7 @@ public abstract class MobileNetworkDatabase extends RoomDatabase { * Query the subscription info by the subscription ID from the SubscriptionInfoEntity * table. */ - public LiveData<SubscriptionInfoEntity> querySubInfoById(String id) { + public SubscriptionInfoEntity querySubInfoById(String id) { return mSubscriptionInfoDao().querySubInfoById(id); } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java index 45966376ea8a..e835125b9b81 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java @@ -37,7 +37,7 @@ public interface SubscriptionInfoDao { @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE " + DataServiceUtils.SubscriptionInfoData.COLUMN_ID + " = :subId") - LiveData<SubscriptionInfoEntity> querySubInfoById(String subId); + SubscriptionInfoEntity querySubInfoById(String subId); @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE " + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS new file mode 100644 index 000000000000..61c73fb733a9 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS @@ -0,0 +1,8 @@ +# Default reviewers for this and subdirectories. +bonianchen@google.com +changbetty@google.com +goldmanj@google.com +wengsu@google.com +zoeychen@google.com + +# Emergency approvers in case the above are not available diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index def7ddc86556..98af15a85238 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -122,6 +122,7 @@ public class SecureSettings { Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index cde4bc42b225..80af69c32b05 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -177,6 +177,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt index 0f037e40b997..06ea381d0c1d 100644 --- a/packages/SystemUI/ktfmt_includes.txt +++ b/packages/SystemUI/ktfmt_includes.txt @@ -27,8 +27,6 @@ -packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt -packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt -packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt --packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt --packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt -packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt -packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt -packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt @@ -683,8 +681,6 @@ -packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt -packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt -packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt -packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt -packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt -packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index d64587dcf362..c29714957318 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml @@ -31,7 +31,8 @@ android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentTop="true" - android:paddingStart="@dimen/clock_padding_start" /> + android:paddingStart="@dimen/clock_padding_start" + android:visibility="invisible" /> <FrameLayout android:id="@+id/lockscreen_clock_view_large" android:layout_width="match_parent" diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml index d5552f6ad44f..1ff549e43360 100644 --- a/packages/SystemUI/res-keyguard/values-af/strings.xml +++ b/packages/SystemUI/res-keyguard/values-af/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Gebruik eerder ’n patroon vir bykomende sekuriteit"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Gebruik eerder ’n PIN vir bykomende sekuriteit"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Gebruik eerder ’n wagwoord vir bykomende sekuriteit"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Toestel is deur administrateur gesluit"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Toestel is handmatig gesluit"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie herken nie"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Verstek"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Borrel"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ontsluit jou toestel om voort te gaan"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml index 533e5a299e4c..f61c8cf64692 100644 --- a/packages/SystemUI/res-keyguard/values-am/strings.xml +++ b/packages/SystemUI/res-keyguard/values-am/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ለተጨማሪ ደህንነት በምትኩ ስርዓተ ጥለት ይጠቀሙ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ለተጨማሪ ደህንነት በምትኩ ፒን ይጠቀሙ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ለተጨማሪ ደህንነት በምትኩ የይለፍ ቃል ይጠቀሙ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"መሣሪያ በአስተዳዳሪ ተቆልፏል"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"መሣሪያ በተጠቃሚው ራሱ ተቆልፏል"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"አልታወቀም"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ነባሪ"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"አረፋ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"አናሎግ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ለመቀጠል መሣሪያዎን ይክፈቱ"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml index 81ce7d3c9361..f3256babf1c6 100644 --- a/packages/SystemUI/res-keyguard/values-ar/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"لمزيد من الأمان، استخدِم النقش بدلاً من ذلك."</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"لمزيد من الأمان، أدخِل رقم التعريف الشخصي بدلاً من ذلك."</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"لمزيد من الأمان، أدخِل كلمة المرور بدلاً من ذلك."</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"تلقائي"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"فقاعة"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ساعة تقليدية"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"يجب فتح قفل الجهاز للمتابعة"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml index 443f666c0faa..f9dc46fa6d53 100644 --- a/packages/SystemUI/res-keyguard/values-as/strings.xml +++ b/packages/SystemUI/res-keyguard/values-as/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"অতিৰিক্ত সুৰক্ষাৰ বাবে, ইয়াৰ পৰিৱৰ্তে আৰ্হি ব্যৱহাৰ কৰক"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"অতিৰিক্ত সুৰক্ষাৰ বাবে, ইয়াৰ পৰিৱৰ্তে পিন ব্যৱহাৰ কৰক"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"অতিৰিক্ত সুৰক্ষাৰ বাবে, ইয়াৰ পৰিৱৰ্তে পাছৱৰ্ড ব্যৱহাৰ কৰক"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্ৰশাসকে ডিভাইচ লক কৰি ৰাখিছে"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইচটো মেনুৱেলভাৱে লক কৰা হৈছিল"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"চিনাক্ত কৰিব পৰা নাই"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ডিফ’ল্ট"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string> <string name="clock_title_analog" msgid="8409262532900918273">"এনাল’গ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"অব্যাহত ৰাখিবলৈ আপোনাৰ ডিভাইচটো আনলক কৰক"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml index e12569715eca..65c1c931a52e 100644 --- a/packages/SystemUI/res-keyguard/values-az/strings.xml +++ b/packages/SystemUI/res-keyguard/values-az/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Əlavə təhlükəsizlik üçün modeldən istifadə edin"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Əlavə təhlükəsizlik üçün PIN istifadə edin"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Əlavə təhlükəsizlik üçün paroldan istifadə edin"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz admin tərəfindən kilidlənib"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihaz əl ilə kilidləndi"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmır"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Defolt"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Qabarcıq"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoq"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Davam etmək üçün cihazınızın kilidini açın"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml index f0d1ef2d6bc3..cf363dfdff6d 100644 --- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Za dodatnu bezbednost koristite šablon"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Za dodatnu bezbednost koristite PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Za dodatnu bezbednost koristite lozinku"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznat"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Podrazumevani"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Mehurići"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da biste nastavili"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml index e1af3eceada8..c2dedf30237f 100644 --- a/packages/SystemUI/res-keyguard/values-be/strings.xml +++ b/packages/SystemUI/res-keyguard/values-be/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"У мэтах дадатковай бяспекі скарыстайце ўзор разблакіроўкі"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"У мэтах дадатковай бяспекі скарыстайце PIN-код"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"У мэтах дадатковай бяспекі скарыстайце пароль"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Прылада заблакіравана адміністратарам"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Прылада была заблакіравана ўручную"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распазнана"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Стандартны"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Бурбалкі"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Са стрэлкамі"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Каб працягнуць, разблакіруйце прыладу"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml index 0b4417ac30e8..546a645b1736 100644 --- a/packages/SystemUI/res-keyguard/values-bg/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За допълнителна сигурност използвайте фигура вместо това"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За допълнителна сигурност използвайте ПИН код вместо това"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За допълнителна сигурност използвайте парола вместо това"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройството е заключено от администратора"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройството бе заключено ръчно"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не е разпознато"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Стандартен"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Балонен"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Аналогов"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Отключете устройството си, за да продължите"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml index 485157984749..7b3df35dda6c 100644 --- a/packages/SystemUI/res-keyguard/values-bn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"অতিরিক্ত সুরক্ষার জন্য, এর বদলে প্যাটার্ন ব্যবহার করুন"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"অতিরিক্ত সুরক্ষার জন্য, এর বদলে পিন ব্যবহার করুন"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"অতিরিক্ত সুরক্ষার জন্য, এর বদলে পাসওয়ার্ড ব্যবহার করুন"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্রশাসক ডিভাইসটি লক করেছেন"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইসটিকে ম্যানুয়ালি লক করা হয়েছে"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"শনাক্ত করা যায়নি"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ডিফল্ট"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string> <string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"চালিয়ে যেতে আপনার ডিভাইস আনলক করুন"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml index 4705b4d9645f..bb9e690c7a48 100644 --- a/packages/SystemUI/res-keyguard/values-bs/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Radi dodatne zaštite, umjesto toga koristite uzorak"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Radi dodatne zaštite, umjesto toga koristite PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Radi dodatne zašitite, umjesto toga koristite lozinku"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Uređaj je zaključao administrator"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurići"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da nastavite"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml index 284eaebfbd8e..1c81c6008ab6 100644 --- a/packages/SystemUI/res-keyguard/values-ca/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Per a més seguretat, utilitza el patró"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Per a més seguretat, utilitza el PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Per a més seguretat, utilitza la contrasenya"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueja el dispositiu per continuar"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml index 6b4f60742964..9a6178c8ffec 100644 --- a/packages/SystemUI/res-keyguard/values-cs/strings.xml +++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Z bezpečnostních důvodů raději použijte gesto"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Z bezpečnostních důvodů raději použijte PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Z bezpečnostních důvodů raději použijte heslo"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zařízení je uzamknuto administrátorem"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zařízení bylo ručně uzamčeno"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznáno"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Výchozí"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Pokud chcete pokračovat, odemkněte zařízení"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml index 85238dfdab0f..aac1b8323383 100644 --- a/packages/SystemUI/res-keyguard/values-da/strings.xml +++ b/packages/SystemUI/res-keyguard/values-da/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Øg sikkerheden ved at bruge dit oplåsningsmønter i stedet"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Øg sikkerheden ved at bruge din pinkode i stedet"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Øg sikkerheden ved at bruge din adgangskode i stedet"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheden er blevet låst af administratoren"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheden blev låst manuelt"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke genkendt"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lås din enhed op for at fortsætte"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml index 18befed429a9..5a340ff2f91a 100644 --- a/packages/SystemUI/res-keyguard/values-de/strings.xml +++ b/packages/SystemUI/res-keyguard/values-de/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Verwende für mehr Sicherheit stattdessen dein Muster"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Verwende für mehr Sicherheit stattdessen deine PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Verwende für mehr Sicherheit stattdessen dein Passwort"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Gerät vom Administrator gesperrt"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Gerät manuell gesperrt"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nicht erkannt"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Gerät entsperren, um fortzufahren"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml index 65b844862a9f..973139f52419 100644 --- a/packages/SystemUI/res-keyguard/values-el/strings.xml +++ b/packages/SystemUI/res-keyguard/values-el/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Για πρόσθετη ασφάλεια, χρησιμοποιήστε εναλλακτικά μοτίβο"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Για πρόσθετη ασφάλεια, χρησιμοποιήστε εναλλακτικά PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Για πρόσθετη ασφάλεια, χρησιμοποιήστε εναλλακτικά κωδικό πρόσβασης"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Η συσκευή κλειδώθηκε από τον διαχειριστή"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Η συσκευή κλειδώθηκε με μη αυτόματο τρόπο"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Δεν αναγνωρίστηκε"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Προεπιλογή"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ξεκλειδώστε τη συσκευή σας για να συνεχίσετε"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml index 588f1b501f53..41eaa38949e8 100644 --- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Default"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml index 08fc8d66c98d..c8ba237088f2 100644 --- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Default"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml index 588f1b501f53..41eaa38949e8 100644 --- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Default"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml index 588f1b501f53..41eaa38949e8 100644 --- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml +++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Default"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml index c71a67865925..6314d902e2cf 100644 --- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml +++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para seguridad adicional, usa un patrón"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para seguridad adicional, usa un PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para seguridad adicional, usa una contraseña"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se bloqueó de forma manual"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoció"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloquea tu dispositivo para continuar"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml index c6ee6980ff3b..5aecf84ce90e 100644 --- a/packages/SystemUI/res-keyguard/values-es/strings.xml +++ b/packages/SystemUI/res-keyguard/values-es/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para mayor seguridad, usa el patrón"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para mayor seguridad, usa el PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para mayor seguridad, usa la contraseña"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se ha bloqueado manualmente"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoce"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloquea tu dispositivo para continuar"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml index 071ede8a0b25..9306ff61774c 100644 --- a/packages/SystemUI/res-keyguard/values-et/strings.xml +++ b/packages/SystemUI/res-keyguard/values-et/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Kasutage tugevama turvalisuse huvides hoopis mustrit"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Kasutage tugevama turvalisuse huvides hoopis PIN-koodi"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Kasutage tugevama turvalisuse huvides hoopis parooli"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administraator lukustas seadme"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Seade lukustati käsitsi"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tuvastatud"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Vaikenumbrilaud"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Mull"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jätkamiseks avage oma seade"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml index 9b8e65b1dde7..4ebe0f0baf14 100644 --- a/packages/SystemUI/res-keyguard/values-eu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PINa idatzi beharko duzu gailua berrabiarazten denean"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Babestuago egoteko, erabili eredua"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Babestuago egoteko, erabili PINa"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Babestuago egoteko, erabili pasahitza"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Puxikak"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogikoa"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Aurrera egiteko, desblokeatu gailua"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml index 3583f1e60e7a..e9a2e87374c3 100644 --- a/packages/SystemUI/res-keyguard/values-fa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"برای امنیت بیشتر، بهجای آن از الگو استفاده کنید"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"برای امنیت بیشتر، بهجای آن از پین استفاده کنید"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"برای امنیت بیشتر، بهجای آن از گذرواژه استفاده کنید"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"دستگاه توسط سرپرست سیستم قفل شده است"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"دستگاه بهصورت دستی قفل شده است"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"شناسایی نشد"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"پیشفرض"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string> <string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"برای ادامه، قفل دستگاهتان را باز کنید"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml index a0ac6df2f029..e80869a757ed 100644 --- a/packages/SystemUI/res-keyguard/values-fi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Lisäsuojaa saat, kun käytät sen sijaan kuviota"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Lisäsuojaa saat, kun käytät sen sijaan PIN-koodia"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Lisäsuojaa saat, kun käytät sen sijaan salasanaa"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Järjestelmänvalvoja lukitsi laitteen."</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Laite lukittiin manuaalisesti"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tunnistettu"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Oletus"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Kupla"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoginen"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jatka avaamalla laitteen lukitus"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml index ec00ba3ae887..92d0617f0a10 100644 --- a/packages/SystemUI/res-keyguard/values-fr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pour plus de sécurité, utilisez plutôt un schéma"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pour plus de sécurité, utilisez plutôt un code"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pour plus de sécurité, utilisez plutôt un mot de passe"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Appareil verrouillé par l\'administrateur"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Appareil verrouillé manuellement"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non reconnu"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Déverrouillez votre appareil pour continuer"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml index a3f8e86cd5e6..776e90a32fa1 100644 --- a/packages/SystemUI/res-keyguard/values-gl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Utiliza un padrón para obter maior seguranza"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Utiliza un PIN para obter maior seguranza"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Utiliza un contrasinal para obter maior seguranza"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"O administrador bloqueou o dispositivo"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo bloqueouse manualmente"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non se recoñeceu"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Burbulla"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analóxico"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloquea o dispositivo para continuar"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml index c97fe017ec60..a8b9a3af8e86 100644 --- a/packages/SystemUI/res-keyguard/values-gu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"વધારાની સુરક્ષા માટે, તેના બદલે પૅટર્નનો ઉપયોગ કરો"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"વધારાની સુરક્ષા માટે, તેના બદલે પિનનો ઉપયોગ કરો"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"વધારાની સુરક્ષા માટે, તેના બદલે પાસવર્ડનો ઉપયોગ કરો"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"વ્યવસ્થાપકે ઉપકરણ લૉક કરેલું છે"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ઉપકરણ મેન્યુઅલી લૉક કર્યું હતું"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"ઓળખાયેલ નથી"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ડિફૉલ્ટ"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"બબલ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"એનાલોગ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ચાલુ રાખવા માટે તમારા ડિવાઇસને અનલૉક કરો"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml index 128300488f27..47560dd0ca7d 100644 --- a/packages/SystemUI/res-keyguard/values-hi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ज़्यादा सुरक्षा के लिए, इसके बजाय पैटर्न का इस्तेमाल करें"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ज़्यादा सुरक्षा के लिए, इसके बजाय पिन का इस्तेमाल करें"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ज़्यादा सुरक्षा के लिए, इसके बजाय पासवर्ड का इस्तेमाल करें"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"व्यवस्थापक ने डिवाइस को लॉक किया है"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिवाइस को मैन्युअल रूप से लॉक किया गया था"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"पहचान नहीं हो पाई"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"डिफ़ॉल्ट"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string> <string name="clock_title_analog" msgid="8409262532900918273">"एनालॉग"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"जारी रखने के लिए डिवाइस अनलॉक करें"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml index 7a14a80e9bb3..efd1cbb07c93 100644 --- a/packages/SystemUI/res-keyguard/values-hr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Za dodatnu sigurnost upotrijebite uzorak"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Za dodatnu sigurnost upotrijebite PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Za dodatnu sigurnost upotrijebite zaporku"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurić"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da biste nastavili"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml index a4fbf537d331..0421ff84f36e 100644 --- a/packages/SystemUI/res-keyguard/values-hu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"A nagyobb biztonság érdekében használjon inkább mintát"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"A nagyobb biztonság érdekében használjon inkább PIN-kódot"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"A nagyobb biztonság érdekében használjon inkább jelszót"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"A rendszergazda zárolta az eszközt"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Az eszközt manuálisan lezárták"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nem ismerhető fel"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Alapértelmezett"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Buborék"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analóg"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"A folytatáshoz oldja fel az eszközét"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml index 086eeb939941..d421c2998883 100644 --- a/packages/SystemUI/res-keyguard/values-hy/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Լրացուցիչ անվտանգության համար օգտագործեք նախշ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Լրացուցիչ անվտանգության համար օգտագործեք PIN կոդ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Լրացուցիչ անվտանգության համար օգտագործեք գաղտնաբառ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Սարքը կողպված է ադմինիստրատորի կողմից"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Սարքը կողպվել է ձեռքով"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Չհաջողվեց ճանաչել"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Կանխադրված"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Պղպջակ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Անալոգային"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Շարունակելու համար ապակողպեք ձեր սարքը"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml index b43a0322fc7a..2061e85d8be7 100644 --- a/packages/SystemUI/res-keyguard/values-in/strings.xml +++ b/packages/SystemUI/res-keyguard/values-in/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Untuk keamanan tambahan, gunakan pola"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Untuk keamanan tambahan, gunakan PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Untuk keamanan tambahan, gunakan sandi"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Default"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Buka kunci perangkat untuk melanjutkan"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml index 8bad961dae4b..ae3da5785603 100644 --- a/packages/SystemUI/res-keyguard/values-is/strings.xml +++ b/packages/SystemUI/res-keyguard/values-is/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Fyrir aukið öryggi skaltu nota mynstur í staðinn"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Fyrir aukið öryggi skaltu nota PIN-númer í staðinn"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Fyrir aukið öryggi skaltu nota aðgangsorð í staðinn"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Kerfisstjóri læsti tæki"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Tækinu var læst handvirkt"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Þekktist ekki"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Sjálfgefið"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Blaðra"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Með vísum"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Taktu tækið úr lás til að halda áfram"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml index 186177ff6d49..d1feea6a1f5d 100644 --- a/packages/SystemUI/res-keyguard/values-it/strings.xml +++ b/packages/SystemUI/res-keyguard/values-it/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Per maggior sicurezza, usa invece la sequenza"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Per maggior sicurezza, usa invece il PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Per maggior sicurezza, usa invece la password"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloccato dall\'amministratore"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Il dispositivo è stato bloccato manualmente"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non riconosciuto"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Predefinito"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bolla"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogico"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Sblocca il dispositivo per continuare"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml index 123cc39e47da..b56042a0309f 100644 --- a/packages/SystemUI/res-keyguard/values-ka/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"დამატებითი უსაფრთხოებისთვის გამოიყენეთ ნიმუში"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"დამატებითი უსაფრთხოებისთვის გამოიყენეთ PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"დამატებითი უსაფრთხოებისთვის გამოიყენეთ პაროლი"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"მოწყობილობა ჩაკეტილია ადმინისტრატორის მიერ"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"მოწყობილობა ხელით ჩაიკეტა"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"არ არის ამოცნობილი"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ნაგულისხმევი"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ბუშტი"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ანალოგური"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"გასაგრძელებლად განბლოკეთ თქვენი მოწყობილობა"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml index 8daca5c6d59a..a4024dea3b7c 100644 --- a/packages/SystemUI/res-keyguard/values-kk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Қосымша қауіпсіздік үшін өрнекті пайдаланыңыз."</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Қосымша қауіпсіздік үшін PIN кодын пайдаланыңыз."</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Қосымша қауіпсіздік үшін құпия сөзді пайдаланыңыз."</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Құрылғыны әкімші құлыптаған"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Құрылғы қолмен құлыпталды"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Танылмады"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Әдепкі"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Көпіршік"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Аналогтық"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Жалғастыру үшін құрылғының құлпын ашыңыз"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml index 73f507c91ece..329912ab824d 100644 --- a/packages/SystemUI/res-keyguard/values-km/strings.xml +++ b/packages/SystemUI/res-keyguard/values-km/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ដើម្បីសុវត្ថិភាពបន្ថែម សូមប្រើលំនាំជំនួសវិញ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ដើម្បីសុវត្ថិភាពបន្ថែម សូមប្រើកូដ PIN ជំនួសវិញ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ដើម្បីសុវត្ថិភាពបន្ថែម សូមប្រើពាក្យសម្ងាត់ជំនួសវិញ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកគ្រប់គ្រង"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកប្រើផ្ទាល់"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"មិនអាចសម្គាល់បានទេ"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"លំនាំដើម"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ពពុះ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"អាណាឡូក"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ដោះសោឧបករណ៍របស់អ្នកដើម្បីបន្ត"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml index c279ceac244e..d42d08d9fe6c 100644 --- a/packages/SystemUI/res-keyguard/values-kn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ, ಬದಲಿಗೆ ಪ್ಯಾಟರ್ನ್ ಅನ್ನು ಬಳಸಿ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ, ಬದಲಿಗೆ ಪಿನ್ ಬಳಸಿ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ, ಬದಲಿಗೆ ಪಾಸ್ವರ್ಡ್ ಅನ್ನು ಬಳಸಿ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ನಿರ್ವಾಹಕರು ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಿದ್ದಾರೆ"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ಸಾಧನವನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ಡೀಫಾಲ್ಟ್"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ಬಬಲ್"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ಅನಲಾಗ್"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ಮುಂದುವರಿಸಲು, ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಿ"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml index 4c058edbf688..e916fee1c3d8 100644 --- a/packages/SystemUI/res-keyguard/values-ko/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"보안 강화를 위해 대신 패턴 사용"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"보안 강화를 위해 대신 PIN 사용"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"보안 강화를 위해 대신 비밀번호 사용"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"관리자가 기기를 잠갔습니다."</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"기기가 수동으로 잠금 설정되었습니다."</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"인식할 수 없음"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"기본"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"버블"</string> <string name="clock_title_analog" msgid="8409262532900918273">"아날로그"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"기기를 잠금 해제하여 계속"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml index 7c7099e1cf61..88abd1e632eb 100644 --- a/packages/SystemUI/res-keyguard/values-ky/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Кошумча коопсуздук үчүн анын ордуна графикалык ачкычты колдонуңуз"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Кошумча коопсуздук үчүн анын ордуна PIN кодду колдонуңуз"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Кошумча коопсуздук үчүн анын ордуна сырсөздү колдонуңуз"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Түзмөктү администратор кулпулап койгон"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Түзмөк кол менен кулпуланды"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Таанылган жок"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Демейки"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Көбүк"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Аналог"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Улантуу үчүн түзмөгүңүздүн кулпусун ачыңыз"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml index 5a6df42884b4..5001c3091df5 100644 --- a/packages/SystemUI/res-keyguard/values-lo/strings.xml +++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ, ໃຫ້ໃຊ້ຮູບແບບແທນ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ, ໃຫ້ໃຊ້ PIN ແທນ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ, ໃຫ້ໃຊ້ລະຫັດຜ່ານແທນ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ອຸປະກອນຖືກລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ອຸປະກອນຖືກສັ່ງໃຫ້ລັອກ"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"ບໍ່ຮູ້ຈັກ"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ຄ່າເລີ່ມຕົ້ນ"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ຟອງ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ໂມງເຂັມ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ປົດລັອກອຸປະກອນຂອງທ່ານເພື່ອສືບຕໍ່"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml index 4d98fd17baf8..20f6ad2f7500 100644 --- a/packages/SystemUI/res-keyguard/values-lt/strings.xml +++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Papildomai saugai užtikrinti geriau naudokite atrakinimo piešinį"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Papildomai saugai užtikrinti geriau naudokite PIN kodą"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Papildomai saugai užtikrinti geriau naudokite slaptažodį"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Įrenginį užrakino administratorius"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Įrenginys užrakintas neautomatiškai"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Neatpažinta"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Numatytasis"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Debesėlis"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoginis"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Įrenginio atrakinimas norint tęsti"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml index 2660a069c949..7012c1671413 100644 --- a/packages/SystemUI/res-keyguard/values-lv/strings.xml +++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Papildu drošībai izmantojiet kombināciju"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Papildu drošībai izmantojiet PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Papildu drošībai izmantojiet paroli"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrators bloķēja ierīci."</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Ierīce tika bloķēta manuāli."</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nav atpazīts"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Noklusējums"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuļi"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogais"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lai turpinātu, atbloķējiet ierīci"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml index e62b4356822d..7919773548e8 100644 --- a/packages/SystemUI/res-keyguard/values-ml/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം പാറ്റേൺ വരയ്ക്കേണ്ടതുണ്ട്"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം പിൻ നൽകേണ്ടതുണ്ട്"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം പാസ്വേഡ് നൽകേണ്ടതുണ്ട്"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"അധിക സുരക്ഷയ്ക്കായി, പകരം പാറ്റേൺ ഉപയോഗിക്കുക"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"അധിക സുരക്ഷയ്ക്കായി, പകരം പിൻ ഉപയോഗിക്കുക"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"അധിക സുരക്ഷയ്ക്കായി, പകരം പാസ്വേഡ് ഉപയോഗിക്കുക"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ഉപകരണം അഡ്മിൻ ലോക്കുചെയ്തു"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ഉപകരണം നേരിട്ട് ലോക്കുചെയ്തു"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"തിരിച്ചറിയുന്നില്ല"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ഡിഫോൾട്ട്"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ബബിൾ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"അനലോഗ്"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"തുടരാൻ നിങ്ങളുടെ ഉപകരണം അൺലോക്ക് ചെയ്യുക"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml index 1454b20d3797..580b547ae309 100644 --- a/packages/SystemUI/res-keyguard/values-mr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"अतिरिक्त सुरक्षेसाठी, त्याऐवजी पॅटर्न वापरा"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"अतिरिक्त सुरक्षेसाठी, त्याऐवजी पिन वापरा"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"अतिरिक्त सुरक्षेसाठी, त्याऐवजी पासवर्ड वापरा"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिव्हाइस मॅन्युअली लॉक केले होते"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"ओळखले नाही"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"डीफॉल्ट"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string> <string name="clock_title_analog" msgid="8409262532900918273">"अॅनालॉग"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"पुढे सुरू ठेवण्यासाठी तुमचे डिव्हाइस अनलॉक करा"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml index a6d1af9368ec..c179dcb61f24 100644 --- a/packages/SystemUI/res-keyguard/values-ms/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Untuk keselamatan tambahan, gunakan corak"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Untuk keselamatan tambahan, gunakan PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Untuk keselamatan tambahan, gunakan kata laluan"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Peranti dikunci oleh pentadbir"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Peranti telah dikunci secara manual"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Lalai"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Gelembung"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Buka kunci peranti anda untuk meneruskan"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml index 5617a1188a40..7c69bdd47d75 100644 --- a/packages/SystemUI/res-keyguard/values-my/strings.xml +++ b/packages/SystemUI/res-keyguard/values-my/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ထပ်ဆောင်းလုံခြုံရေးအတွက် ၎င်းအစား ပုံစံသုံးပါ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ထပ်ဆောင်းလုံခြုံရေးအတွက် ၎င်းအစား ပင်နံပါတ်သုံးပါ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ထပ်ဆောင်းလုံခြုံရေးအတွက် ၎င်းအစား စကားဝှက်သုံးပါ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"မူလ"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ပူဖောင်းကွက်"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ရိုးရိုး"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ရှေ့ဆက်ရန် သင့်စက်ကိုဖွင့်ပါ"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml index 0ad9e951b1e4..e394d1fc8135 100644 --- a/packages/SystemUI/res-keyguard/values-nb/strings.xml +++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Bruk mønster i stedet, for å øke sikkerheten"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Bruk PIN-kode i stedet, for å øke sikkerheten"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Bruk passord i stedet, for å øke sikkerheten"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheten er låst av administratoren"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten ble låst manuelt"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke gjenkjent"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lås opp enheten for å fortsette"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml index 196b74a5658b..9f329e923e35 100644 --- a/packages/SystemUI/res-keyguard/values-ne/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"अतिरिक्त सुरक्षाका लागि यो प्रमाणीकरण विधिको साटो प्याटर्न प्रयोग गर्नुहोस्"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"अतिरिक्त सुरक्षाका लागि यो प्रमाणीकरण विधिको साटो पिन प्रयोग गर्नुहोस्"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"अतिरिक्त सुरक्षाका लागि यो प्रमाणीकरण विधिको साटो पासवर्ड प्रयोग गर्नुहोस्"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकले यन्त्रलाई लक गर्नुभएको छ"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"यन्त्रलाई म्यानुअल तरिकाले लक गरिएको थियो"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"डिफल्ट"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string> <string name="clock_title_analog" msgid="8409262532900918273">"एनालग"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"आफ्नो डिभाइस अनलक गरी जारी राख्नुहोस्"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml index 747b3bbd9128..579824a2a734 100644 --- a/packages/SystemUI/res-keyguard/values-nl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Gebruik in plaats daarvan het patroon voor extra beveiliging"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Gebruik in plaats daarvan de pincode voor extra beveiliging"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Gebruik in plaats daarvan het wachtwoord voor extra beveiliging"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Apparaat vergrendeld door beheerder"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Apparaat is handmatig vergrendeld"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Niet herkend"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Standaard"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bel"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ontgrendel je apparaat om door te gaan"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml index bf1a359a2c75..5c3fff71ba22 100644 --- a/packages/SystemUI/res-keyguard/values-pa/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ, ਇਸਦੀ ਬਜਾਏ ਪੈਟਰਨ ਵਰਤੋ"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ, ਇਸਦੀ ਬਜਾਏ ਪਿੰਨ ਵਰਤੋ"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ, ਇਸਦੀ ਬਜਾਏ ਪਾਸਵਰਡ ਵਰਤੋ"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ਡੀਵਾਈਸ ਨੂੰ ਹੱਥੀਂ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"ਬੁਲਬੁਲਾ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ਐਨਾਲੌਗ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕਰੋ"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml index c49149baa93a..373638601416 100644 --- a/packages/SystemUI/res-keyguard/values-pl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Ze względów bezpieczeństwa użyj wzoru"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Ze względów bezpieczeństwa użyj kodu PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Ze względów bezpieczeństwa użyj hasła"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Urządzenie zablokowane przez administratora"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Urządzenie zostało zablokowane ręcznie"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie rozpoznano"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Domyślna"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bąbelkowy"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogowy"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Odblokuj urządzenie, aby kontynuować"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml index 547224e781b1..67ae0fcf7a8b 100644 --- a/packages/SystemUI/res-keyguard/values-ro/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pentru mai multă securitate, folosește modelul"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pentru mai multă securitate, folosește codul PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pentru mai multă securitate, folosește parola"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Deblochează dispozitivul pentru a continua"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml index e5862c358002..82df4cb781cb 100644 --- a/packages/SystemUI/res-keyguard/values-si/strings.xml +++ b/packages/SystemUI/res-keyguard/values-si/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්යයි"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්යයි"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්යයි"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"අතිරේක ආරක්ෂාව සඳහා, ඒ වෙනුවට රටාව භාවිතා කරන්න"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"අතිරේක ආරක්ෂාව සඳහා, ඒ වෙනුවට PIN භාවිතා කරන්න"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"අතිරේක ආරක්ෂාව සඳහා, ඒ වෙනුවට මුරපදය භාවිතා කරන්න"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ඔබගේ පරිපාලක විසින් උපාංගය අගුළු දමා ඇත"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"උපාංගය හස්තීයව අගුලු දමන ලදී"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"හඳුනා නොගන්නා ලදී"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"පෙරනිමි"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"බුබුළ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ප්රතිසමය"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ඉදිරියට යාමට ඔබේ උපාංගය අගුළු හරින්න"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml index efe4ec864448..2d8b3b15c42b 100644 --- a/packages/SystemUI/res-keyguard/values-sk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"V rámci zvýšenia zabezpečenia použite radšej vzor"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"V rámci zvýšenia zabezpečenia použite radšej PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"V rámci zvýšenia zabezpečenia použite radšej heslo"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zariadenie zamkol správca"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zariadenie bolo uzamknuté ručne"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznané"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Predvolený"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analógový"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ak chcete pokračovať, odomknite zariadenie"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml index 52726c225498..4c4ea062b50a 100644 --- a/packages/SystemUI/res-keyguard/values-sl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Za dodatno varnost raje uporabite vzorec."</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Za dodatno varnost raje uporabite kodo PIN."</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Za dodatno varnost raje uporabite geslo."</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Napravo je zaklenil skrbnik"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Naprava je bila ročno zaklenjena"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ni prepoznano"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Privzeto"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Mehurček"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogno"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Za nadaljevanje odklenite napravo"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml index a0a55944eced..78e217dfe331 100644 --- a/packages/SystemUI/res-keyguard/values-sq/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Për më shumë siguri, përdor motivin më mirë"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Për më shumë siguri, përdor kodin PIN më mirë"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Për më shumë siguri, përdor fjalëkalimin më mirë"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Pajisja është e kyçur nga administratori"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Pajisja është kyçur manualisht"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nuk njihet"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"E parazgjedhur"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Flluskë"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analoge"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Shkyç pajisjen tënde për të vazhduar"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml index e634fdcb586e..80d8755b8fee 100644 --- a/packages/SystemUI/res-keyguard/values-sr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За додатну безбедност користите шаблон"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За додатну безбедност користите PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За додатну безбедност користите лозинку"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Администратор је закључао уређај"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уређај је ручно закључан"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Није препознат"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Подразумевани"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Мехурићи"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Аналогни"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Откључајте уређај да бисте наставили"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml index fc9beb1286a6..b5548b95ba56 100644 --- a/packages/SystemUI/res-keyguard/values-sv/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste rita mönster när du har startat om enheten"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"För ytterligare säkerhet använder du mönstret i stället"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"För ytterligare säkerhet använder du pinkoden i stället"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"För ytterligare säkerhet använder du lösenordet i stället"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratören har låst enheten"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten har låsts manuellt"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Identifierades inte"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bubbla"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lås upp enheten för att fortsätta"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml index bcab24b013bb..02af18ecb0a5 100644 --- a/packages/SystemUI/res-keyguard/values-sw/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Kwa usalama wa ziada, tumia mchoro badala yake"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Kwa usalama wa ziada, tumia PIN badala yake"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Kwa usalama wa ziada, tumia nenosiri badala yake"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Msimamizi amefunga kifaa"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Umefunga kifaa mwenyewe"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Haitambuliwi"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Chaguomsingi"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Kiputo"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analogi"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Fungua kifaa chako ili uendelee"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml index 88d5760e7f6c..0d32d46626c4 100644 --- a/packages/SystemUI/res-keyguard/values-ta/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"கூடுதல் பாதுகாப்பிற்குப் பேட்டர்னைப் பயன்படுத்தவும்"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"கூடுதல் பாதுகாப்பிற்குப் பின்னை (PIN) பயன்படுத்தவும்"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"கூடுதல் பாதுகாப்பிற்குக் கடவுச்சொல்லைப் பயன்படுத்தவும்"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"நிர்வாகி சாதனத்தைப் பூட்டியுள்ளார்"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"பயனர் சாதனத்தைப் பூட்டியுள்ளார்"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"அடையாளங்காணபடவில்லை"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"இயல்பு"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"பபிள்"</string> <string name="clock_title_analog" msgid="8409262532900918273">"அனலாக்"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"தொடர, சாதனத்தை அன்லாக் செய்யுங்கள்"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml index 3a0111a193bd..f519daf10fd5 100644 --- a/packages/SystemUI/res-keyguard/values-te/strings.xml +++ b/packages/SystemUI/res-keyguard/values-te/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్వర్డ్ను నమోదు చేయాలి"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"అదనపు సెక్యూరిటీ కోసం, బదులుగా ఆకృతిని ఉపయోగించండి"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"అదనపు సెక్యూరిటీ కోసం, బదులుగా PINను ఉపయోగించండి"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"అదనపు సెక్యూరిటీ కోసం, బదులుగా పాస్వర్డ్ను ఉపయోగించండి"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"పరికరం నిర్వాహకుల ద్వారా లాక్ చేయబడింది"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"పరికరం మాన్యువల్గా లాక్ చేయబడింది"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"గుర్తించలేదు"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string> <string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"కొనసాగించడానికి మీ పరికరాన్ని అన్లాక్ చేయండి"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml index e5207623adc2..80dae8c9f124 100644 --- a/packages/SystemUI/res-keyguard/values-tr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Ek güvenlik için bunun yerine desen kullanın"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Ek güvenlik için bunun yerine PIN kullanın"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Ek güvenlik için bunun yerine şifre kullanın"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz, yönetici tarafından kilitlendi"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihazın manuel olarak kilitlendi"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmadı"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Varsayılan"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Baloncuk"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Devam etmek için cihazınızın kilidini açın"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml index 613181d6c96f..ff594ae88905 100644 --- a/packages/SystemUI/res-keyguard/values-uk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"З міркувань додаткової безпеки скористайтеся ключем"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"З міркувань додаткової безпеки скористайтеся PIN-кодом"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"З міркувань додаткової безпеки скористайтеся паролем"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Адміністратор заблокував пристрій"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Пристрій заблоковано вручну"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не розпізнано"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"За умовчанням"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Бульбашковий"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Аналоговий"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Розблокуйте пристрій, щоб продовжити"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml index a122f8537611..9308260b790f 100644 --- a/packages/SystemUI/res-keyguard/values-ur/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاس ورڈ درکار ہوتا ہے"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"اضافی سیکیورٹی کے لئے، اس کے بجائے پیٹرن استعمال کریں"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"اضافی سیکیورٹی کے لئے، اس کے بجائے PIN استعمال کریں"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"اضافی سیکیورٹی کے لئے، اس کے بجائے پاس ورڈ استعمال کریں"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"ڈیفالٹ"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"بلبلہ"</string> <string name="clock_title_analog" msgid="8409262532900918273">"اینالاگ"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"جاری رکھنے کے لئے اپنا آلہ غیر مقفل کریں"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml index e7c9295815ad..2771adaba169 100644 --- a/packages/SystemUI/res-keyguard/values-vi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Để tăng cường bảo mật, hãy sử dụng hình mở khoá"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Để tăng cường bảo mật, hãy sử dụng mã PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Để tăng cường bảo mật, hãy sử dụng mật khẩu"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Thiết bị đã bị quản trị viên khóa"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Thiết bị đã bị khóa theo cách thủ công"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Không nhận dạng được"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Mặc định"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Bong bóng"</string> <string name="clock_title_analog" msgid="8409262532900918273">"Đồng hồ kim"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Mở khoá thiết bị của bạn để tiếp tục"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml index d37d645b15ee..fb9283801ad2 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"为增强安全性,请改用图案"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"为增强安全性,请改用 PIN 码"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"为增强安全性,请改用密码"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理员已锁定设备"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"此设备已手动锁定"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"无法识别"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"默认"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string> <string name="clock_title_analog" msgid="8409262532900918273">"指针"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解锁设备才能继续操作"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml index 9dbb8f2dac73..49050e54b3ac 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"為提升安全性,請改用圖案"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"為提升安全性,請改用 PIN"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"為提升安全性,請改用密碼"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"裝置已由管理員鎖定"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"使用者已手動將裝置上鎖"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"未能識別"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"預設"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string> <string name="clock_title_analog" msgid="8409262532900918273">"指針"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解鎖裝置以繼續"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml index ebb88e13194b..e5a363c3633d 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"為強化安全性,請改用解鎖圖案"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"為強化安全性,請改用 PIN 碼"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"為強化安全性,請改用密碼"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理員已鎖定裝置"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"裝置已手動鎖定"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"無法識別"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"預設"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string> <string name="clock_title_analog" msgid="8409262532900918273">"類比"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解鎖裝置才能繼續操作"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml index 57e56f713536..72ca6c0f8efc 100644 --- a/packages/SystemUI/res-keyguard/values-zu/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml @@ -78,12 +78,9 @@ <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string> <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string> <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string> - <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) --> - <skip /> - <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) --> - <skip /> + <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Ukuze uthole ukuvikeleka okwengeziwe, sebenzisa iphetheni esikhundleni salokho"</string> + <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Ukuze uthole ukuvikeleka okwengeziwe, sebenzisa i-PIN esikhundleni salokho"</string> + <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Ukuze uthole ukuvikeleka okwengeziwe, sebenzisa iphasiwedi esikhundleni salokho"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Idivayisi ikhiywe ngumlawuli"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Idivayisi ikhiywe ngokwenza"</string> <string name="kg_face_not_recognized" msgid="7903950626744419160">"Akwaziwa"</string> @@ -93,6 +90,5 @@ <string name="clock_title_default" msgid="6342735240617459864">"Okuzenzekelayo"</string> <string name="clock_title_bubble" msgid="2204559396790593213">"Ibhamuza"</string> <string name="clock_title_analog" msgid="8409262532900918273">"I-Analog"</string> - <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) --> - <skip /> + <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Vula idivayisi yakho ukuze uqhubeke"</string> </resources> diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml index c526d9cc8dd3..9b8b611558fe 100644 --- a/packages/SystemUI/res/layout/media_session_view.xml +++ b/packages/SystemUI/res/layout/media_session_view.xml @@ -44,6 +44,15 @@ android:background="@drawable/qs_media_outline_album_bg" /> + <com.android.systemui.ripple.MultiRippleView + android:id="@+id/touch_ripple_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_expanded" + app:layout_constraintStart_toStartOf="@id/album_art" + app:layout_constraintEnd_toEndOf="@id/album_art" + app:layout_constraintTop_toTopOf="@id/album_art" + app:layout_constraintBottom_toBottomOf="@id/album_art" /> + <!-- Guideline for output switcher --> <androidx.constraintlayout.widget.Guideline android:id="@+id/center_vertical_guideline" diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml index c2c79cb0f34b..78884ffbe1a2 100644 --- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml +++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml @@ -14,58 +14,84 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.android.systemui.user.UserSwitcherRootView +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:id="@+id/user_switcher_root" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginVertical="40dp" - android:layout_marginHorizontal="60dp"> + android:orientation="vertical"> - <androidx.constraintlayout.helper.widget.Flow - android:id="@+id/flow" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:flow_horizontalBias="0.5" - app:flow_verticalAlign="center" - app:flow_wrapMode="chain" - app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap" - app:flow_verticalGap="44dp" - app:flow_horizontalStyle="packed"/> + <ScrollView + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:fillViewport="true"> - <TextView - android:id="@+id/cancel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center" - app:layout_constraintHeight_min="48dp" - app:layout_constraintEnd_toStartOf="@+id/add" - app:layout_constraintBottom_toBottomOf="parent" - android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding" - android:textSize="@dimen/user_switcher_fullscreen_button_text_size" - android:textColor="?androidprv:attr/colorAccentPrimary" - android:text="@string/cancel" /> + <com.android.systemui.user.UserSwitcherRootView + android:id="@+id/user_switcher_grid_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="40dp" + android:paddingHorizontal="60dp"> - <TextView - android:id="@+id/add" - style="@style/Widget.Dialog.Button.BorderButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center" - android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding" - android:text="@string/add" - android:textColor="?androidprv:attr/colorAccentPrimary" - android:textSize="@dimen/user_switcher_fullscreen_button_text_size" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHeight_min="48dp" /> -</com.android.systemui.user.UserSwitcherRootView> + <androidx.constraintlayout.helper.widget.Flow + android:id="@+id/flow" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:flow_horizontalBias="0.5" + app:flow_verticalAlign="center" + app:flow_wrapMode="chain" + app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap" + app:flow_verticalGap="44dp" + app:flow_horizontalStyle="packed"/> + </com.android.systemui.user.UserSwitcherRootView> + + </ScrollView> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="96dp" + android:orientation="horizontal" + android:gravity="center_vertical|end" + android:paddingEnd="48dp"> + + <TextView + android:id="@+id/cancel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:minHeight="48dp" + android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding" + android:textSize="@dimen/user_switcher_fullscreen_button_text_size" + android:textColor="?androidprv:attr/colorAccentPrimary" + android:text="@string/cancel" /> + + <Space + android:layout_width="24dp" + android:layout_height="0dp" + /> + + <TextView + android:id="@+id/add" + style="@style/Widget.Dialog.Button.BorderButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding" + android:text="@string/add" + android:textColor="?androidprv:attr/colorAccentPrimary" + android:textSize="@dimen/user_switcher_fullscreen_button_text_size" + android:visibility="gone" + android:minHeight="48dp" /> + + </LinearLayout> + +</LinearLayout> diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 3dc85d70ffb2..2f2780b3546d 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikerinstellings"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Maak toe"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Gekoppel"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Skakel mobiele data af?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Jy sal nie deur <xliff:g id="CARRIER">%s</xliff:g> toegang tot data of die internet hê nie. Internet sal net deur Wi-Fi beskikbaar wees."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jou diensverskaffer"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Instellings kan nie jou antwoord verifieer nie omdat \'n program \'n toestemmingversoek verberg."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Dit kan inligting van <xliff:g id="APP">%1$s</xliff:g> af lees"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Vergrootglasvensterinstellings"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik om toeganklikheidkenmerke oop te maak Pasmaak of vervang knoppie in Instellings.\n\n"<annotation id="link">"Bekyk instellings"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Skuif knoppie na kant om dit tydelik te versteek"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Beweeg na links bo"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Beweeg na regs bo"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Beweeg na links onder"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Beweeg na regs onder"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Beweeg na rand en versteek"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Beweeg weg van rand en wys"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"wissel"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Toestelkontroles"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Kies program om kontroles by te voeg"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Gekoppel"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data sal nie outomaties koppel nie"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding nie"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen ander netwerke beskikbaar nie"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index fb60f9b8aedd..3ccb68668514 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"የተጠቃሚ ቅንብሮች"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ተከናውኗል"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ዝጋ"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"ተገናኝቷል"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"የተንቀሳቃሽ ስልክ ውሂብ ይጥፋ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"በ<xliff:g id="CARRIER">%s</xliff:g> በኩል የውሂብ ወይም የበይነመረቡ መዳረሻ አይኖረዎትም። በይነመረብ በWi-Fi በኩል ብቻ ነው የሚገኝ የሚሆነው።"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"የእርስዎ አገልግሎት አቅራቢ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"አንድ መተግበሪያ የፍቃድ ጥያቄ እያገደ ስለሆነ ቅንብሮች ጥያቄዎን ማረጋገጥ አይችሉም።"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"የማጉያ መስኮት ቅንብሮች"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"የተደራሽነት ባህሪያትን ለመክፈት መታ ያድርጉ። ይህንን አዝራር በቅንብሮች ውስጥ ያብጁ ወይም ይተኩ።\n\n"<annotation id="link">"ቅንብሮችን አሳይ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ለጊዜው ለመደበቅ አዝራሩን ወደ ጠርዝ ያንቀሳቅሱ"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ወደ ላይኛው ግራ አንቀሳቅስ"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ወደ ላይኛው ቀኝ አንቀሳቅስ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"የግርጌውን ግራ አንቀሳቅስ"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ታችኛውን ቀኝ አንቀሳቅስ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ወደ ጠርዝ አንቀሳቅስ እና ደደብቅ"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ጠርዙን ወደ ውጭ አንቀሳቅስ እና አሳይ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ቀያይር"</string> <string name="quick_controls_title" msgid="6839108006171302273">"የመሣሪያ መቆጣጠሪያዎች"</string> <string name="controls_providers_title" msgid="6879775889857085056">"መቆጣጠሪያዎችን ለማከል መተግበሪያ ይምረጡ"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"የተንቀሳቃሽ ስልክ ውሂብ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"ተገናኝቷል"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"የተንቀሳቃሽ ስልክ ውሂብ በራስ-ሰር አይገናኝም"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ግንኙነት የለም"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ሌላ አውታረ መረብ የሉም"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index f8567c7b7868..c90fb254d3f6 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"إعدادات المستخدم"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"تم"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"إغلاق"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"متصل"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"هل تريد إيقاف بيانات الجوّال؟"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"لن تتمكّن من استخدام البيانات أو الإنترنت من خلال <xliff:g id="CARRIER">%s</xliff:g>. ولن يتوفر اتصال الإنترنت إلا عبر Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"مشغّل شبكة الجوّال"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"لا يمكن للإعدادات التحقق من ردك لأن هناك تطبيقًا يحجب طلب الإذن."</string> <string name="slice_permission_title" msgid="3262615140094151017">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- يستطيع قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"إعدادات نافذة مكبّر الشاشة"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"انقر لفتح ميزات تسهيل الاستخدام. يمكنك تخصيص هذا الزر أو استبداله من الإعدادات.\n\n"<annotation id="link">"عرض الإعدادات"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"يمكنك نقل الزر إلى الحافة لإخفائه مؤقتًا."</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"نقل إلى أعلى يمين الشاشة"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"نقل إلى أعلى يسار الشاشة"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نقل إلى أسفل يمين الشاشة"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نقل إلى أسفل يسار الشاشة"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"نقله إلى الحافة وإخفاؤه"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"نقله إلى خارج الحافة وإظهاره"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"إيقاف/تفعيل"</string> <string name="quick_controls_title" msgid="6839108006171302273">"التحكم بالجهاز"</string> <string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"بيانات الجوّال"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"متصلة بالإنترنت"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"لن يتم تلقائيًا الاتصال ببيانات الجوّال."</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"لا يتوفّر اتصال بالإنترنت"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"لا تتوفّر شبكات أخرى."</string> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 4fca5f2e1541..c363eee10440 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যৱহাৰকাৰীৰ ছেটিং"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন কৰা হ’ল"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"বন্ধ কৰক"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"সংযোগ কৰা হ’ল"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ম’বাইল ডেটা অফ কৰিবনে?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"আপুনি <xliff:g id="CARRIER">%s</xliff:g>ৰ জৰিয়তে ডেটা সংযোগ বা ইণ্টাৰনেট সংযোগ নাপাব। কেৱল ৱাই-ফাইৰ যোগেৰে ইণ্টাৰনেট উপলব্ধ হ\'ব।"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপোনাৰ বাহক"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"এটা এপে অনুমতি বিচাৰি কৰা অনুৰোধ এটা ঢাকি ধৰা বাবে ছেটিঙৰ পৰা আপোনাৰ উত্তৰ সত্যাপন কৰিব পৰা নাই।"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"বিবৰ্ধকৰ ৱিণ্ড’ৰ ছেটিং"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"সাধ্য সুবিধাসমূহ খুলিবলৈ টিপক। ছেটিঙত এই বুটামটো কাষ্টমাইজ অথবা সলনি কৰক।\n\n"<annotation id="link">"ছেটিং চাওক"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"বুটামটোক সাময়িকভাৱে লুকুৱাবলৈ ইয়াক একেবাৰে কাষলৈ লৈ যাওক"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"শীৰ্ষৰ বাওঁফালে নিয়ক"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"শীৰ্ষৰ সোঁফালে নিয়ক"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"তলৰ বাওঁফালে নিয়ক"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"তলৰ সোঁফালে নিয়ক"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"কাষলৈ নিয়ক আৰু লুকুৱাওক"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"কাষৰ বাহিৰলৈ নিয়ক আৰু দেখুৱাওক"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ট’গল কৰক"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ"</string> <string name="controls_providers_title" msgid="6879775889857085056">"নিয়ন্ত্ৰণসমূহ যোগ কৰিবলৈ এপ্ বাছনি কৰক"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ম’বাইল ডেটা"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"সংযোজিত হৈ আছে"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ম’বাইল ডেটা স্বয়ংক্ৰিয়ভাৱে সংযুক্ত নহ’ব"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"সংযোগ নাই"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনো নেটৱৰ্ক উপলব্ধ নহয়"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 7fa603fa44d1..c5f143bd5b54 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"İstifadəçi ayarları"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Hazır"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Bağlayın"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Qoşulu"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil data söndürülsün?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ilə data və ya internetə daxil ola bilməyəcəksiniz. İnternet yalnız Wi-Fi ilə əlçatan olacaq."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorunuz"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Tətbiq icazə sorğusunu gizlətdiyi üçün Ayarlar cavabınızı doğrulaya bilməz."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Böyüdücü pəncərə ayarları"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Əlçatımlılıq funksiyalarını açmaq üçün toxunun. Ayarlarda bu düyməni fərdiləşdirin və ya dəyişdirin.\n\n"<annotation id="link">"Ayarlara baxın"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düyməni müvəqqəti gizlətmək üçün kənara çəkin"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuxarıya sola köçürün"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Yuxarıya sağa köçürün"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Aşağıya sola köçürün"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Aşağıya sağa köçürün"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"İçəri keçirib gizlədin"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Kənara daşıyıb göstərin"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"keçirin"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Cihaz kontrolları"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Kontrol əlavə etmək üçün tətbiq seçin"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Qoşulub"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil data avtomatik qoşulmayacaq"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yoxdur"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Heç bir başqa şəbəkə əlçatan deyil"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index ffe49b2c195a..e61b692219bf 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisnička podešavanja"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Povezan"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite da isključite mobilne podatke?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ili internetu preko mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo preko WiFi veze."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mobilni operater"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Podešavanja ne mogu da verifikuju vaš odgovor jer aplikacija skriva zahtev za dozvolu."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Podešavanja prozora za uvećanje"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za funkcije pristupačnosti. Prilagodite ili zamenite ovo dugme u Podešavanjima.\n\n"<annotation id="link">"Podešavanja"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomerite dugme do ivice da biste ga privremeno sakrili"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premesti gore levo"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premesti gore desno"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premesti dole levo"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premesti dole desno"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premesti do ivice i sakrij"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Premesti izvan ivice i prikaži"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"uključite/isključite"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju za dodavanje kontrola"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nije uspelo autom. povezivanje preko mob. podataka"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Veza nije uspostavljena"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 3e76985abbf7..827370f9db37 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налады карыстальніка"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Гатова"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрыць"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Падлучана"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Выключыць мабільную перадачу даных?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"У вас не будзе доступу да даных ці інтэрнэту праз аператара <xliff:g id="CARRIER">%s</xliff:g>. Інтэрнэт будзе даступны толькі праз Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш аператар"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Праграма хавае запыт на дазвол, таму ваш адказ немагчыма спраўдзіць у Наладах."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць зрэзы праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Налады акна лупы"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Націсніце, каб адкрыць спецыяльныя магчымасці. Рэгулюйце ці замяняйце кнопку ў Наладах.\n\n"<annotation id="link">"Прагляд налад"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Каб часова схаваць кнопку, перамясціце яе на край"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перамясціць лявей і вышэй"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перамясціць правей і вышэй"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перамясціць лявей і ніжэй"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перамясціць правей і ніжэй"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перамясціць на край і схаваць"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Перамясціць за край і паказаць"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"уключыць/выключыць"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Элементы кіравання прыладай"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Выберыце праграму для дадавання элементаў кіравання"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мабільная перадача даных"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Падключана"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мабільная перадача даных не ўключаецца аўтаматычна"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма падключэння"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Больш няма даступных сетак"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index 572e13fe567e..880445bdfc13 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Цветове: инверт."</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Потребителски настройки"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затваряне"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Установена е връзка"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се изключат ли мобилните данни?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Няма да можете да използвате данни или интернет чрез <xliff:g id="CARRIER">%s</xliff:g>. Ще имате достъп до интернет само през Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"оператора си"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"От Настройки не може да се получи потвърждение за отговора ви, защото заявката за разрешение се прикрива от приложение."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Искате ли да разрешите на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Настройки за инструмента за увеличаване на прозорци"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Докоснете, за да отворите функциите за достъпност. Персон./заменете бутона от настройките.\n\n"<annotation id="link">"Преглед на настройките"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете бутона до края, за да го скриете временно"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Преместване горе вляво"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Преместване горе вдясно"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Преместване долу вляво"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Преместване долу вдясно"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Преместване в края и скриване"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Преместване в края и показване"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"превключване"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Контроли за устройството"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Изберете приложение, за да добавите контроли"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни данни"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Свързано"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Връзката за мобилни данни няма да е автоматична"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма връзка"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Няма други налични мрежи"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 645f6ab84dfe..c74fc69660a1 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যবহারকারী সেটিংস"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন হয়েছে"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"বন্ধ করুন"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"সংযুক্ত হয়েছে"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"মোবাইল ডেটা বন্ধ করবেন?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"আপনি \'<xliff:g id="CARRIER">%s</xliff:g>\'-এর মাধ্যমে ডেটা অথবা ইন্টারনেট অ্যাক্সেস করতে পারবেন না। শুধুমাত্র ওয়াই-ফাইয়ের মাধ্যমেই ইন্টারনেট অ্যাক্সেস করা যাবে।"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপনার পরিষেবা প্রদানকারী"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"একটি অ্যাপ কোনও অনুমোদনের অনুরোধকে ঢেকে দিচ্ছে, তাই সেটিংস থেকে আপনার প্রতিক্রিয়া যাচাই করা যাচ্ছে না।"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"\'ম্যাগনিফায়ার উইন্ডো\' সেটিংস"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"অ্যাক্সেসিবিলিটি ফিচার খুলতে ট্যাপ করুন। কাস্টমাইজ করুন বা সেটিংসে এই বোতামটি সরিয়ে দিন।\n\n"<annotation id="link">"সেটিংস দেখুন"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"এটি অস্থায়ীভাবে লুকাতে বোতামটি কোণে সরান"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"উপরে বাঁদিকে সরান"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"উপরে ডানদিকে সরান"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"নিচে বাঁদিকে সরান"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"নিচে ডান দিকে সরান"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"প্রান্তে যান ও আড়াল করুন"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"প্রান্ত থেকে সরান এবং দেখুন"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"টগল করুন"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইস কন্ট্রোল"</string> <string name="controls_providers_title" msgid="6879775889857085056">"কন্ট্রোল যোগ করতে অ্যাপ বেছে নিন"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"মোবাইল ডেটা"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"কানেক্ট করা আছে"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"মোবাইল ডেটা নিজে থেকে কানেক্ট হবে না"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"কানেকশন নেই"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনও নেটওয়ার্ক উপলভ্য নেই"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 183dfe071b7a..5356d74fe196 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boja"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljajte korisnicima"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Povezano"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti prijenos podataka na mobilnoj mreži?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ni internetu putem mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem WiFi-ja."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš operater"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Postavke ne mogu potvrditi vaš odgovor jer aplikacija zaklanja zahtjev za odobrenje."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Postavke prozora povećala"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite da otvorite funkcije pristupačnosti. Prilagodite ili zamijenite dugme u Postavkama.\n\n"<annotation id="link">"Postavke"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Premjestite dugme do ivice da ga privremeno sakrijete"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pomjeranje gore lijevo"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pomjeranje gore desno"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pomjeranje dolje lijevo"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pomjeranje dolje desno"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pomjeranje do ivice i sakrivanje"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pomjeranje izvan ivice i prikaz"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktiviranje/deaktiviranje"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju da dodate kontrole"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Prijenos podataka na mobilnoj mreži"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Prijenos podataka se neće automatski povezati"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani s mrežom"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Druge mreže nisu dostupne"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index daa63e61ce16..b61ee2c3b7e0 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuració d\'usuari"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Fet"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tanca"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connectat"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vols desactivar les dades mòbils?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"No tindràs accés a dades ni a Internet mitjançant <xliff:g id="CARRIER">%s</xliff:g>. Internet només estarà disponible per Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"el teu operador de telefonia mòbil"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Com que hi ha una aplicació que oculta una sol·licitud de permís, no es pot verificar la teva resposta des de la configuració."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuració de la finestra de la lupa"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca per obrir funcions d\'accessibilitat. Personalitza o substitueix el botó a Configuració.\n\n"<annotation id="link">"Mostra"</annotation>"."</string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mou el botó a l\'extrem per amagar-lo temporalment"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mou a dalt a l\'esquerra"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mou a dalt a la dreta"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mou a baix a l\'esquerra"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mou a baix a la dreta"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mou dins de les vores i amaga"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mou fora de les vores i mostra"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"commuta"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Controls de dispositius"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Selecciona l\'aplicació per afegir controls"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dades mòbils"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connectat"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Les dades mòbils no es connectaran automàticament"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sense connexió"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hi ha cap altra xarxa disponible"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index cba944849c59..ee364681ba7e 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Uživatelské nastavení"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zavřít"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Připojeno"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vypnout mobilní data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Prostřednictvím <xliff:g id="CARRIER">%s</xliff:g> nebudete moci používat data ani internet. Internet bude dostupný pouze přes Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vašeho operátora"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Žádost o oprávnění je blokována jinou aplikací. Nastavení proto vaši odpověď nemůže ověřit."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavení okna zvětšení"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Klepnutím otevřete funkce přístupnosti. Tlačítko lze upravit nebo nahradit v Nastavení.\n\n"<annotation id="link">"Nastavení"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Přesunutím tlačítka k okraji ho dočasně skryjete"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Přesunout vlevo nahoru"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Přesunout vpravo nahoru"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Přesunout vlevo dolů"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Přesunout vpravo dolů"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Přesunout k okraji a skrýt"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Přesunout okraj ven a zobrazit"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"přepnout"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Ovládání zařízení"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikaci, pro kterou chcete přidat ovládací prvky"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilní data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Připojeno"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilní data se nebudou připojovat automaticky"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Žádné připojení"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Žádné další sítě nejsou k dispozici"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index a879cbb7db28..220014e4d21b 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brugerindstillinger"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrer brugere"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Udfør"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Luk"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Tilsluttet"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du deaktivere mobildata?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du vil ikke have data- eller internetadgang via <xliff:g id="CARRIER">%s</xliff:g>. Der vil kun være adgang til internettet via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"dit mobilselskab"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Indstillinger kan ikke bekræfte dit svar, da en app dækker for en anmodning om tilladelse."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Indstillinger for lupvindue"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryk for at åbne hjælpefunktioner. Tilpas eller erstat denne knap i Indstillinger.\n\n"<annotation id="link">"Se indstillingerne"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flyt knappen til kanten for at skjule den midlertidigt"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flyt op til venstre"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flyt op til højre"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flyt ned til venstre"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flyt ned til højre"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flyt ud til kanten, og skjul"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flyt ud til kanten, og vis"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"slå til/fra"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Enhedsstyring"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Vælg en app for at tilføje styring"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Forbundet"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Ingen automatisk mobildataforbindelse"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Der er ingen forbindelse"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Der er ingen andre tilgængelige netværk"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index fb135ff09eab..0b3af674fd42 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Nutzereinstellungen"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Fertig"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Schließen"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Verbunden"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobile Daten deaktivieren?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kannst dann nicht mehr über <xliff:g id="CARRIER">%s</xliff:g> auf Daten und das Internet zugreifen. Das Internet ist nur noch über WLAN verfügbar."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"deinen Mobilfunkanbieter"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Deine Eingabe wird von \"Einstellungen\" nicht erkannt, weil die Berechtigungsanfrage von einer App verdeckt wird."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Einstellungen für das Vergrößerungsfenster"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tippe, um die Bedienungshilfen aufzurufen. Du kannst diese Schaltfläche in den Einstellungen anpassen oder ersetzen.\n\n"<annotation id="link">"Zu den Einstellungen"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Durch Ziehen an den Rand wird die Schaltfläche zeitweise ausgeblendet"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Nach oben links verschieben"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Nach rechts oben verschieben"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Nach unten links verschieben"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Nach unten rechts verschieben"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"An den Rand verschieben und verbergen"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Vom Rand verschieben und anzeigen"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Wechseln"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Gerätesteuerung"</string> <string name="controls_providers_title" msgid="6879775889857085056">"App zum Hinzufügen von Steuerelementen auswählen"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile Daten"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Verbunden"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Keine automatische Verbindung über mobile Daten"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Keine Verbindung"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Keine anderen Netzwerke verfügbar"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 3d1595326030..d707e3ecd6cb 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ρυθμίσεις χρήστη"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Διαχείριση χρηστών"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Τέλος"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Κλείσιμο"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Συνδέθηκε"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Απενεργοποίηση δεδομένων κινητής τηλεφωνίας;"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Δεν θα έχετε πρόσβαση σε δεδομένα ή στο διαδίκτυο μέσω της εταιρείας κινητής τηλεφωνίας <xliff:g id="CARRIER">%s</xliff:g>. Θα έχετε πρόσβαση στο διαδίκτυο μόνο μέσω Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"η εταιρεία κινητής τηλεφωνίας"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Επειδή μια εφαρμογή αποκρύπτει ένα αίτημα άδειας, δεν είναι δυνατή η επαλήθευση της απάντησής σας από τις Ρυθμίσεις."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Να επιτρέπεται στο <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ρυθμίσεις παραθύρου μεγεθυντικού φακού"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Πατήστε για άνοιγμα των λειτουργιών προσβασιμότητας. Προσαρμόστε ή αντικαταστήστε το κουμπί στις Ρυθμίσεις.\n\n"<annotation id="link">"Προβολή ρυθμίσεων"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Μετακινήστε το κουμπί στο άκρο για προσωρινή απόκρυψη"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Μετακίνηση επάνω αριστερά"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Μετακίνηση επάνω δεξιά"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Μετακίνηση κάτω αριστερά"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Μετακίνηση κάτω δεξιά"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Μετακίν. στο άκρο και απόκρυψη"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Μετακ. εκτός άκρου και εμφάν."</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"εναλλαγή"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Στοιχεία ελέγχου συσκευής"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Επιλογή εφαρμογής για προσθήκη στοιχείων ελέγχου"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Δεδομένα κινητής τηλεφωνίας"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Συνδέθηκε"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Χωρίς αυτόματη σύνδεση δεδομένων κινητ. τηλεφωνίας"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Χωρίς σύνδεση"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Δεν υπάρχουν άλλα διαθέσιμα δίκτυα"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index e7166acece78..3aaa5f5152d4 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index fe746d9669f9..fb56d9f34000 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your carrier"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index e7166acece78..3aaa5f5152d4 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index e7166acece78..3aaa5f5152d4 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index b55b74415c8c..ee568382a214 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Color inversion"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Color correction"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string> @@ -727,6 +727,10 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"You wont have access to data or the internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your carrier"</string> + <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"Switch back to <xliff:g id="CARRIER">%s</xliff:g>?"</string> + <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"Mobile data wont automatically switch based on availability"</string> + <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"No thanks"</string> + <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"Yes, switch"</string> <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +789,15 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customize or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation>""</string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string> + <string name="accessibility_floating_button_undo" msgid="511112888715708241">"Undo"</string> + <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{{label} shortcut removed}other{# shortcuts removed}}"</string> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string> + <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Remove"</string> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string> @@ -933,6 +940,8 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string> + <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"Temporarily connected"</string> + <string name="mobile_data_poor_connection" msgid="819617772268371434">"Poor connection"</string> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index e7a6cf1cbc64..6160caf86228 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corregir colores"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración del usuario"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Listo"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Cerrar"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Deseas desactivar los datos móviles?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu proveedor"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Como una app está bloqueando una solicitud de permiso, Configuración no puede verificar tu respuesta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información sobre <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración de la ventana de ampliación"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Presiona para abrir las funciones de accesibilidad. Personaliza o cambia botón en Config.\n\n"<annotation id="link">"Ver config"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover arriba a la derecha"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover abajo a la izquierda"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover abajo a la derecha"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover fuera de borde y ocultar"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover fuera de borde y mostrar"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar o desactivar"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Controles de dispositivos"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Elige la app para agregar los controles"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Conexión establecida"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"No se conectarán automáticamente los datos móviles"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index e8218b09852e..ed9e7d90cd6f 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ajustes de usuario"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Hecho"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Cerrar"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Desactivar datos móviles?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu operador"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Una aplicación impide ver una solicitud de permiso, por lo que Ajustes no puede verificar tu respuesta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración de la ventana de la lupa"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir funciones de accesibilidad. Personaliza o sustituye este botón en Ajustes.\n\n"<annotation id="link">"Ver ajustes"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover arriba a la derecha"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover abajo a la izquierda"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover abajo a la derecha"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover al borde y ocultar"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover al borde y mostrar"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar/desactivar"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Elige una aplicación para añadir controles"</string> @@ -839,7 +854,7 @@ <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string> <string name="controls_media_button_play" msgid="2705068099607410633">"Reproducir"</string> <string name="controls_media_button_pause" msgid="8614887780950376258">"Pausar"</string> - <string name="controls_media_button_prev" msgid="8126822360056482970">"Pista anterior"</string> + <string name="controls_media_button_prev" msgid="8126822360056482970">"Canción anterior"</string> <string name="controls_media_button_next" msgid="6662636627525947610">"Siguiente pista"</string> <string name="controls_media_button_connecting" msgid="3138354625847598095">"Conectando"</string> <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Los datos móviles no se conectarán automáticamente"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 11a9dc917142..417c2c2b5bdc 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kasutaja seaded"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sule"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Ühendatud"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Kas lülitada mobiilne andmeside välja?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Pärast seda pole teil operaatori <xliff:g id="CARRIER">%s</xliff:g> kaudu juurdepääsu andmesidele ega internetile. Internet on saadaval ainult WiFi kaudu."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"teie operaator"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Seaded ei saa teie vastust kinnitada, sest rakendus varjab loataotlust."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Luubi akna seaded"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Puudutage juurdepääsufunktsioonide avamiseks. Kohandage nuppu või asendage see seadetes.\n\n"<annotation id="link">"Kuva seaded"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Teisaldage nupp serva, et see ajutiselt peita"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Teisalda üles vasakule"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Teisalda üles paremale"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Teisalda alla vasakule"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Teisalda alla paremale"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Teisalda serva ja kuva"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Teisalda servast eemale ja kuva"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"lülita"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Seadmete juhikud"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Valige juhtelementide lisamiseks rakendus"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilne andmeside"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Ühendatud"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilset andmesideühendust ei looda automaatselt"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ühendus puudub"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ühtegi muud võrku pole saadaval"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 3714781f522c..ee1ba2367219 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kolore-alderantzikatzea"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Erabiltzaile-ezarpenak"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Eginda"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Itxi"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Konektatuta"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Datu-konexioa desaktibatu nahi duzu?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> erabilita ezingo dituzu erabili datuak edo Internet. Wifi-sare baten bidez soilik konektatu ahal izango zara Internetera."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Zure operadorea"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikazio bat baimen-eskaera oztopatzen ari denez, ezarpenek ezin dute egiaztatu erantzuna."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Luparen leihoaren ezarpenak"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erabilerraztasun-eginbideak irekitzeko, sakatu hau. Ezarpenetan pertsonalizatu edo ordez dezakezu botoia.\n\n"<annotation id="link">"Ikusi ezarpenak"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Eraman botoia ertzera aldi baterako ezkutatzeko"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Eraman goialdera, ezkerretara"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Eraman goialdera, eskuinetara"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Eraman behealdera, ezkerretara"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Eraman behealdera, eskuinetara"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Eraman ertzera eta ezkutatu"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Atera ertzetik eta erakutsi"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aldatu"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Gailuak kontrolatzeko widgetak"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Aukeratu aplikazio bat kontrolatzeko aukerak gehitzeko"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datu-konexioa"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> (<xliff:g id="NETWORKMODE">%2$s</xliff:g>)"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Konektatuta"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Ez da automatikoki aktibatuko datu-konexioa"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Konexiorik gabe"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ez dago beste sare erabilgarririk"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 183a9edc82fd..62d73ae464f2 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"تنظیمات کاربر"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"تمام"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"بستن"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"متصل"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"داده تلفن همراه خاموش شود؟"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"نمیتوانید ازطریق <xliff:g id="CARRIER">%s</xliff:g> به داده یا اینترنت دسترسی داشته باشید. اینترنت فقط ازطریق Wi-Fi در دسترس خواهد بود."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"شرکت مخابراتی شما"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"چون برنامهای درحال ایجاد تداخل در درخواست مجوز است، «تنظیمات» نمیتواند پاسخ شما را تأیید کند."</string> <string name="slice_permission_title" msgid="3262615140094151017">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکههای <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- میتواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"تنظیمات پنجره ذرهبین"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"برای باز کردن ویژگیهای دسترسپذیری ضربه بزنید. در تنظیمات این دکمه را سفارشی یا جایگزین کنید\n\n"<annotation id="link">"تنظیمات"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"برای پنهان کردن موقتی دکمه، آن را به لبه ببرید"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"انتقال به بالا سمت راست"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"انتقال به بالا سمت چپ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"انتقال به پایین سمت راست"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"انتقال به پایین سمت چپ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"انتقال به لبه و پنهان کردن"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"انتقال به خارج از لبه و نمایش"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"روشن/ خاموش کردن"</string> <string name="quick_controls_title" msgid="6839108006171302273">"کنترلهای دستگاه"</string> <string name="controls_providers_title" msgid="6879775889857085056">"انتخاب برنامه برای افزودن کنترلها"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"داده تلفن همراه"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"متصل است"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"داده تلفن همراه بهطور خودکار متصل نخواهد شد"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"اتصال برقرار نیست"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"شبکه دیگری وجود ندارد"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index df050150c4aa..bfb450fd7d04 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Käyttäjäasetukset"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sulje"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Yhdistetty"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Laitetaanko mobiilidata pois päältä?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ei enää tarjoa pääsyä dataan eikä internetyhteyttä, joka on saatavilla vain Wi-Fin kautta."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operaattorisi"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Sovellus peittää käyttöoikeuspyynnön, joten Asetukset ei voi vahvistaa valintaasi."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ikkunan suurennuksen asetukset"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Avaa esteettömyysominaisuudet napauttamalla. Yksilöi tai vaihda painike asetuksista.\n\n"<annotation id="link">"Avaa asetukset"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Piilota painike tilapäisesti siirtämällä se reunaan"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Siirrä vasempaan yläreunaan"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Siirrä oikeaan yläreunaan"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Siirrä vasempaan alareunaan"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Siirrä oikeaan alareunaan"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Siirrä reunaan ja piilota"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Siirrä pois reunasta ja näytä"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"vaihda"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Laitehallinta"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilidata"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Yhdistetty"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilidata ei yhdisty automaattisesti"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ei yhteyttä"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ei muita verkkoja käytettävissä"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index c5c941bf38b1..b2d925c9037e 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Terminé"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fermer"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connecté"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données cellulaires?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données ni à Internet avec <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre fournisseur de services"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Une application obscurcit une demande d\'autorisation, alors Paramètres ne peut pas vérifier votre réponse."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Paramètres de la fenêtre de loupe"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Touchez pour ouvrir fonction. d\'access. Personnalisez ou remplacez bouton dans Param.\n\n"<annotation id="link">"Afficher param."</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacez le bouton vers le bord pour le masquer temporairement"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer dans coin sup. droit"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer dans coin inf. gauche"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer dans coin inf. droit"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Éloigner du bord et masquer"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"basculer"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Données cellulaires"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connexion active"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Aucune connexion auto. des données cellulaires"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau n\'est accessible"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 1a2b877da3da..12a88c642de8 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"OK"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fermer"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connecté"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données mobiles ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données mobiles ni à Internet via <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre opérateur"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"L\'application Paramètres ne peut pas valider votre réponse, car une application masque la demande d\'autorisation."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Paramètres de la fenêtre d\'agrandissement"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Appuyez pour ouvrir fonctionnalités d\'accessibilité. Personnalisez ou remplacez bouton dans paramètres.\n\n"<annotation id="link">"Voir paramètres"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacer le bouton vers le bord pour le masquer temporairement"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer en haut à droite"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer en bas à gauche"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer en bas à droite"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Rapprocher du bord et masquer"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activer/désactiver"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Données mobiles"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connecté"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Pas de connexion automatique des données mobiles"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau disponible"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index e5def1ec0ca1..d18d596d5356 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración de usuario"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Feito"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Pechar"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Queres desactivar os datos móbiles?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Non terás acceso aos datos nin a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Internet só estará dispoñible mediante a wifi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o teu operador"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Dado que unha aplicación se superpón sobre unha solicitude de permiso, a configuración non pode verificar a túa resposta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Queres permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler información da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración da ventá da lupa"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir as funcións de accesibilidade. Cambia este botón en Configuración.\n\n"<annotation id="link">"Ver configuración"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Para ocultar temporalmente o botón, móveo ata o bordo"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover á parte super. esquerda"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover á parte superior dereita"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover á parte infer. esquerda"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover á parte inferior dereita"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover ao bordo e ocultar"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover fóra do bordo e mostrar"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar/desactivar"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Escolle unha aplicación para engadir controis"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móbiles"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectada"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Os datos móbiles non se conectarán automaticamente"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sen conexión"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Non hai outras redes dispoñibles"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 552b0491bdb8..034c161f487c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"વપરાશકર્તા સેટિંગ"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"થઈ ગયું"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"બંધ કરો"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"કનેક્ટ થયેલું"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"મોબાઇલ ડેટા બંધ કરીએ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"તમને <xliff:g id="CARRIER">%s</xliff:g> મારફતે ડેટા અથવા ઇન્ટરનેટનો ઍક્સેસ મળશે નહીં. ઇન્ટરનેટ માત્ર વાઇ-ફાઇ દ્વારા ઉપલબ્ધ થશે."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"તમારા કૅરિઅર"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"કોઈ ઍપ પરવાનગી વિનંતીને અસ્પષ્ટ કરતી હોવાને કારણે, સેટિંગ તમારા પ્રતિસાદને ચકાસી શકતું નથી."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- મારાથી <xliff:g id="APP">%1$s</xliff:g>ની માહિતી વાંચી શકાતી નથી"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"મેગ્નિફાયર વિન્ડોના સેટિંગ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ઍક્સેસિબિલિટી સુવિધાઓ ખોલવા માટે ટૅપ કરો. સેટિંગમાં આ બટનને કસ્ટમાઇઝ કરો અથવા બદલો.\n\n"<annotation id="link">"સેટિંગ જુઓ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"તેને હંગામી રૂપે ખસેડવા માટે બટનને કિનારી પર ખસેડો"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ઉપર ડાબે ખસેડો"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ઉપર જમણે ખસેડો"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"નીચે ડાબે ખસેડો"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"નીચે જમણે ખસેડો"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"કિનારી પર ખસેડો અને છુપાવો"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"કિનારીથી ખસેડો અને બતાવો"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ટૉગલ કરો"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ડિવાઇસનાં નિયંત્રણો"</string> <string name="controls_providers_title" msgid="6879775889857085056">"નિયંત્રણો ઉમેરવા માટે ઍપ પસંદ કરો"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"મોબાઇલ ડેટા"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"કનેક્ટ કરેલું"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"મોબાઇલ ડેટા ઑટોમૅટિક રીતે કનેક્ટ થશે નહીં"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"કોઈ કનેક્શન નથી"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"બીજાં કોઈ નેટવર્ક ઉપલબ્ધ નથી"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index c0052d62531e..dc94a8de1fc2 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"उपयोगकर्ता सेटिंग"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"हो गया"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"रद्द करें"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"कनेक्ट है"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करना चाहते हैं?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"आप <xliff:g id="CARRIER">%s</xliff:g> से डेटा या इंटरनेट का इस्तेमाल नहीं कर पाएंगे. इंटरनेट सिर्फ़ वाई-फ़ाई से चलेगा."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"आपको मोबाइल और इंटरनेट सेवा देने वाली कंपनी"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ऐप की वजह से मंज़ूरी के अनुरोध को समझने में दिक्कत हो रही है, इसलिए सेटिंग से आपके जवाब की पुष्टि नहीं हो पा रही है."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- यह <xliff:g id="APP">%1$s</xliff:g> से सूचना पढ़ सकता है"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ज़ूम करने की सुविधा वाली विंडो से जुड़ी सेटिंग"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सुलभता सुविधाएं खोलने के लिए टैप करें. सेटिंग में, इस बटन को बदलें या अपने हिसाब से सेट करें.\n\n"<annotation id="link">"सेटिंग देखें"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटन को कुछ समय छिपाने के लिए, उसे किनारे पर ले जाएं"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सबसे ऊपर बाईं ओर ले जाएं"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"सबसे ऊपर दाईं ओर ले जाएं"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"सबसे नीचे बाईं ओर ले जाएं"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"सबसे नीचे दाईं ओर ले जाएं"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"एज पर ले जाएं और छिपाएं"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"एज से निकालें और दिखाएं"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टॉगल करें"</string> <string name="quick_controls_title" msgid="6839108006171302273">"डिवाइस कंट्रोल"</string> <string name="controls_providers_title" msgid="6879775889857085056">"कंट्रोल जोड़ने के लिए ऐप्लिकेशन चुनें"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट हो गया"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा अपने-आप कनेक्ट नहीं होगा"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"इंटरनेट कनेक्शन नहीं है"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"कोई दूसरा नेटवर्क उपलब्ध नहीं है"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 60f0c8a86712..be5253fc8c07 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljajte korisnicima"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Povezano"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti mobilne podatke?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup mobilnim podacima ili internetu putem operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem Wi-Fija."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš mobilni operater"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Budući da aplikacija prekriva zahtjev za dopuštenje, Postavke ne mogu potvrditi vaš odgovor."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Postavke prozora povećala"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za otvaranje značajki pristupačnosti. Prilagodite ili zamijenite taj gumb u postavkama.\n\n"<annotation id="link">"Pregledajte postavke"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomaknite gumb do ruba da biste ga privremeno sakrili"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premjesti u gornji lijevi kut"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premjesti u gornji desni kut"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premjesti u donji lijevi kut"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premjesti u donji desni kut"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premjesti na rub i sakrij"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Ukloni s ruba i prikaži"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"promijeni"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Odabir aplikacije za dodavanje kontrola"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna veza neće se automatski uspostaviti"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 0f6daa286ec9..cf074380b854 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Felhasználói beállítások"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Kész"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Bezárás"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Csatlakoztatva"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Kikapcsolja a mobiladatokat?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nem lesz adat-, illetve internet-hozzáférése a <xliff:g id="CARRIER">%s</xliff:g> szolgáltatón keresztül. Az internethez csak Wi-Fi-n keresztül csatlakozhat."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"saját mobilszolgáltató"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Mivel az egyik alkalmazás eltakarja az engedélykérést, a Beállítások alkalmazás nem tudja ellenőrizni az Ön válaszát."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nagyítóablak beállításai"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Koppintson a kisegítő lehetőségek megnyitásához. A gombot a Beállításokban módosíthatja.\n\n"<annotation id="link">"Beállítások"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"A gombot a szélre áthelyezve ideiglenesen elrejtheti"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Áthelyezés fel és balra"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Áthelyezés fel és jobbra"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Áthelyezés le és balra"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Áthelyezés le és jobbra"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Áthelyezés a szélen kívül és elrejtés"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Áthelyezés a szélen kívül és mutatás"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"váltás"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Eszközvezérlők"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Válasszon alkalmazást a vezérlők hozzáadásához"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiladat"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g>/<xliff:g id="STATE">%1$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Csatlakozva"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nincs automatikus mobiladat-kapcsolat"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nincs kapcsolat"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nincs több rendelkezésre álló hálózat"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 4724d5c876cc..bfd051b82b47 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Օգտատիրոջ կարգավորումներ"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Պատրաստ է"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Փակել"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Միացված է"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Անջատե՞լ բջջային ինտերնետը"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> օպերատորի բջջային ինտերնետը հասանելի չի լինի: Համացանցից կկարողանաք օգտվել միայն Wi-Fi-ի միջոցով:"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Ձեր"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Քանի որ ներածումն արգելափակված է ինչ-որ հավելվածի կողմից, Կարգավորումները չեն կարող հաստատել ձեր պատասխանը:"</string> <string name="slice_permission_title" msgid="3262615140094151017">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Խոշորացույցի պատուհանի կարգավորումներ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Հատուկ գործառույթները բացելու համար հպեք։ Անհատականացրեք այս կոճակը կարգավորումներում։\n\n"<annotation id="link">"Կարգավորումներ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Կոճակը ժամանակավորապես թաքցնելու համար այն տեղափոխեք էկրանի եզր"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Տեղափոխել վերև՝ ձախ"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Տեղափոխել վերև՝ աջ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Տեղափոխել ներքև՝ ձախ"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Տեղափոխել ներքև՝ աջ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Տեղափոխել եզրից դուրս և թաքցնել"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Տեղափոխել եզրից դուրս և ցուցադրել"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"միացնել/անջատել"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Սարքերի կառավարման տարրեր"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Ընտրեք հավելված` կառավարման տարրեր ավելացնելու համար"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Բջջային ինտերնետ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Միացած է"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Բջջային ինտերնետն ավտոմատ չի միանա"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Կապ չկա"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Այլ հասանելի ցանցեր չկան"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 8fefe4014081..87496cfb1510 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setelan pengguna"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tutup"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Terhubung"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Nonaktifkan data seluler?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan dapat mengakses data atau internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya akan tersedia melalui Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Operator Seluler Anda"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Karena sebuah aplikasi menghalangi permintaan izin, Setelan tidak dapat memverifikasi respons Anda."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setelan jendela kaca pembesar"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketuk untuk membuka fitur aksesibilitas. Sesuaikan atau ganti tombol ini di Setelan.\n\n"<annotation id="link">"Lihat setelan"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pindahkan tombol ke tepi agar tersembunyi untuk sementara"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pindahkan ke kiri atas"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pindahkan ke kanan atas"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pindahkan ke kiri bawah"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pindahkan ke kanan bawah"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pindahkan ke tepi dan sembunyikan"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pindahkan dari tepi dan tampilkan"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alihkan"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kontrol perangkat"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Pilih aplikasi untuk menambahkan kontrol"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Data seluler"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Terhubung"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Data seluler tidak akan terhubung otomatis"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Tidak ada koneksi"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Jaringan lain tidak tersedia"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index d22680e3cb10..3d7e2ea82466 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Notandastillingar"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Lokið"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Loka"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Tengt"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Viltu slökkva á farsímagögnum?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Þú munt ekki hafa aðgang að gögnum eða internetinu í gegnum <xliff:g id="CARRIER">%s</xliff:g>. Aðeins verður hægt að tengjast internetinu með Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"símafyrirtækið þitt"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Stillingar geta ekki staðfest svarið þitt vegna þess að forrit er að fela heimildarbeiðni."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Stillingar stækkunarglugga"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ýttu til að opna aðgengiseiginleika. Sérsníddu eða skiptu hnappinum út í stillingum.\n\n"<annotation id="link">"Skoða stillingar"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Færðu hnappinn að brúninni til að fela hann tímabundið"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Færa efst til vinstri"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Færa efst til hægri"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Færa neðst til vinstri"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Færa neðst til hægri"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Færa að jaðri og fela"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Færa að jaðri og birta"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"kveikja/slökkva"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Tækjastjórnun"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Veldu forrit til að bæta við stýringum"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Farsímagögn"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Tengt"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Farsímagögn tengjast ekki sjálfkrafa"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Engin tenging"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Engin önnur net í boði"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index df6c14f4ed11..111568057cb4 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Impostazioni utente"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Fine"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Chiudi"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Connesso"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Disattivare i dati mobili?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Non avrai accesso ai dati o a Internet tramite <xliff:g id="CARRIER">%s</xliff:g>. Internet sarà disponibile soltanto tramite Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"il tuo operatore"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Un\'app sta oscurando una richiesta di autorizzazione, pertanto Impostazioni non può verificare la tua risposta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Impostazioni della finestra di ingrandimento"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tocca per aprire funzioni di accessibilità. Personalizza o sostituisci il pulsante in Impostazioni.\n\n"<annotation id="link">"Vedi impostazioni"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sposta il pulsante fino al bordo per nasconderlo temporaneamente"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sposta in alto a sinistra"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sposta in alto a destra"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sposta in basso a sinistra"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sposta in basso a destra"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sposta fino a bordo e nascondi"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sposta fuori da bordo e mostra"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"attiva/disattiva"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Controllo dispositivi"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Scegli un\'app per aggiungere controlli"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dati mobili"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Connessione attiva"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nessuna connessione dati mobili automatica"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nessuna connessione"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nessun\'altra rete disponibile"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index f24964ae27a9..1fe4e3e45372 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"הגדרות המשתמש"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ניהול משתמשים"</string> <string name="quick_settings_done" msgid="2163641301648855793">"סיום"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"סגירה"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"מחובר"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"לכבות את חבילת הגלישה?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"לא תהיה לך גישה לנתונים או לאינטרנט באמצעות <xliff:g id="CARRIER">%s</xliff:g>. אינטרנט יהיה זמין רק באמצעות Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"הספק שלך"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"יש אפליקציה שמסתירה את בקשת ההרשאה, ולכן אין אפשרות לאמת את התשובה בהגדרות."</string> <string name="slice_permission_title" msgid="3262615140094151017">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- תהיה לה אפשרות לקרוא מידע מאפליקציית <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ההגדרות של חלון ההגדלה"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"מקישים כדי לפתוח את תכונות הנגישות. אפשר להחליף את הלחצן או להתאים אותו אישית בהגדרות.\n\n"<annotation id="link">"הצגת ההגדרות"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"כדי להסתיר זמנית את הלחצן, יש להזיז אותו לקצה"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"העברה לפינה השמאלית העליונה"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"העברה לפינה הימנית העליונה"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"העברה לפינה השמאלית התחתונה"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"העברה לפינה הימנית התחתונה"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"העברה לשוליים והסתרה"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"העברה מהשוליים והצגה"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"החלפת מצב"</string> <string name="quick_controls_title" msgid="6839108006171302273">"פקדי מכשירים"</string> <string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"חבילת גלישה"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"מחובר"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"החיבור לנתונים סלולריים לא מתבצע באופן אוטומטי"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"אין חיבור"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"אין רשתות זמינות אחרות"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 7ea72237c6b5..b00ceb4f1942 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ユーザー設定"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"完了"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"閉じる"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"接続済み"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"モバイルデータを OFF にしますか?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>でデータやインターネットにアクセスできなくなります。インターネットには Wi-Fi からのみ接続できます。"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"携帯通信会社"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"アプリが許可リクエストを隠しているため、設定側でユーザーの応答を確認できません。"</string> <string name="slice_permission_title" msgid="3262615140094151017">"「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示を「<xliff:g id="APP_0">%1$s</xliff:g>」に許可しますか?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- 「<xliff:g id="APP">%1$s</xliff:g>」からの情報の読み取り"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"拡大鏡ウィンドウの設定"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"タップしてユーザー補助機能を開きます。ボタンのカスタマイズや入れ替えを [設定] で行えます。\n\n"<annotation id="link">"設定を表示"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ボタンを一時的に非表示にするには、端に移動させてください"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"左上に移動"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"右上に移動"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"左下に移動"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"右下に移動"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"端に移動して非表示"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"端から移動して表示"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切り替え"</string> <string name="quick_controls_title" msgid="6839108006171302273">"デバイス コントロール"</string> <string name="controls_providers_title" msgid="6879775889857085056">"コントロールを追加するアプリの選択"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"モバイルデータ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"接続済み"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"モバイルデータには自動接続しません"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"接続なし"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"利用できるネットワークはありません"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 93c9d94e6cea..aab9a327d214 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"მომხმარებლის პარამეტრები"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"დასრულდა"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"დახურვა"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"დაკავშირებულია"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"გსურთ მობილური ინტერნეტის გამორთვა?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"თქვენ არ გექნებათ მობილურ ინტერნეტზე ან ზოგადად ინტერნეტზე წვდომა <xliff:g id="CARRIER">%s</xliff:g>-ის მეშვეობით. ინტერნეტი მხოლოდ Wi-Fi-კავშირის მეშვეობით იქნება ხელმისაწვდომი."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"თქვენი ოპერატორი"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ვინაიდან აპი ფარავს ნებართვის მოთხოვნას, პარამეტრების მიერ თქვენი პასუხი ვერ დასტურდება."</string> <string name="slice_permission_title" msgid="3262615140094151017">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"გადიდების ფანჯრის პარამეტრები"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"შეეხეთ მარტივი წვდომის ფუნქციების გასახსნელად. მოარგეთ ან შეცვალეთ ეს ღილაკი პარამეტრებში.\n\n"<annotation id="link">"პარამეტრების ნახვა"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"გადაიტანეთ ღილაკი კიდეში, რათა დროებით დამალოთ ის"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ზევით და მარცხნივ გადატანა"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ზევით და მარჯვნივ გადატანა"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ქვევით და მარცხნივ გადატანა"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ქვემოთ და მარჯვნივ გადატანა"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"კიდეში გადატანა და დამალვა"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"კიდეში გადატანა და გამოჩენა"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"გადართვა"</string> <string name="quick_controls_title" msgid="6839108006171302273">"მოწყობილ. მართვის საშუალებები"</string> <string name="controls_providers_title" msgid="6879775889857085056">"აირჩიეთ აპი მართვის საშუალებების დასამატებლად"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"მობილური ინტერნეტი"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"დაკავშირებული"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"მობილურ ინტერნეტს ავტომატურად არ დაუკავშირდება"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"კავშირი არ არის"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"სხვა ქსელები მიუწვდომელია"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index e096b7e2b7ac..19bfdf5e0174 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пайдаланушы параметрлері"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Дайын"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Жабу"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Қосылды"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік интернет өшірілсін бе?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> операторы арқылы деректерге немесе интернетке кіре алмайсыз. Интернетке тек Wi-Fi арқылы кіресіз."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"операторыңыз"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Басқа қолданба рұқсат сұрауын жасырып тұрғандықтан, параметрлер жауабыңызды растай алмайды."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Бұл <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ұлғайтқыш терезесінің параметрлері"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Арнайы мүмкіндікті ашу үшін түртіңіз. Түймені параметрден реттеңіз не ауыстырыңыз.\n\n"<annotation id="link">"Параметрді көру"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Түймені уақытша жасыру үшін оны шетке қарай жылжытыңыз."</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жоғарғы сол жаққа жылжыту"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Жоғарғы оң жаққа жылжыту"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Төменгі сол жаққа жылжыту"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Төменгі оң жаққа жылжыту"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Шетке жылжыту және жасыру"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Шетке жылжыту және көрсету"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ауыстыру"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Құрылғыны басқару элементтері"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері қосылатын қолданбаны таңдаңыз"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильдік интернет"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Жалғанды"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобильдік интернет автоматты түрде қосылмайды."</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыс жоқ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Басқа қолжетімді желі жоқ"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index aa641e47630c..186244b7b803 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាសពណ៌"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការកែតម្រូវពណ៌"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ការកំណត់អ្នកប្រើប្រាស់"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"រួចរាល់"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"បិទ"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"បានភ្ជាប់"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"បិទទិន្នន័យទូរសព្ទចល័ត?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"អ្នកនឹងមិនមានសិទ្ធិចូលប្រើទិន្នន័យ ឬអ៊ីនធឺណិតតាមរយៈ <xliff:g id="CARRIER">%s</xliff:g> បានឡើយ។ អ៊ីនធឺណិតនឹងអាចប្រើបានតាមរយៈ Wi-Fi តែប៉ុណ្ណោះ។"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ក្រុមហ៊ុនសេវាទូរសព្ទរបស់អ្នក"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ការកំណត់មិនអាចផ្ទៀងផ្ទាត់ការឆ្លើយតបរបស់អ្នកបានទេ ដោយសារកម្មវិធីកំពុងបាំងសំណើសុំការអនុញ្ញាត។"</string> <string name="slice_permission_title" msgid="3262615140094151017">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញស្ថិតិប្រើប្រាស់របស់ <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- វាអាចអានព័ត៌មានពី <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ការកំណត់វិនដូកម្មវិធីពង្រីក"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ចុចដើម្បីបើកមុខងារភាពងាយស្រួល។ ប្ដូរ ឬប្ដូរប៊ូតុងនេះតាមបំណងនៅក្នុងការកំណត់។\n\n"<annotation id="link">"មើលការកំណត់"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ផ្លាស់ទីប៊ូតុងទៅគែម ដើម្បីលាក់វាជាបណ្ដោះអាសន្ន"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងឆ្វេង"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងស្ដាំ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ផ្លាស់ទីទៅខាងក្រោមផ្នែកខាងឆ្វេង"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ផ្លាស់ទីទៅខាងក្រោមផ្នែកខាងស្ដាំ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ផ្លាស់ទីទៅផ្នែកខាងចុង រួចលាក់"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ផ្លាស់ទីចេញពីផ្នែកខាងចុង រួចបង្ហាញ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"បិទ/បើក"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string> <string name="controls_providers_title" msgid="6879775889857085056">"ជ្រើសរើសកម្មវិធីដែលត្រូវបញ្ចូលផ្ទាំងគ្រប់គ្រង"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ទិន្នន័យទូរសព្ទចល័ត"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"បានភ្ជាប់"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ទិន្នន័យទូរសព្ទចល័តនឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិទេ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"មិនមានការតភ្ជាប់ទេ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"មិនមានបណ្ដាញផ្សេងទៀតដែលអាចប្រើបានទេ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index ab3f379e37d3..3341f8d7f7e2 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್ವರ್ಶನ್"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ಬಳಕೆದಾರರ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ಮುಗಿದಿದೆ"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ಮುಚ್ಚಿರಿ"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ಮೊಬೈಲ್ ಡೇಟಾ ಆಫ್ ಮಾಡಬೇಕೆ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"ನೀವು <xliff:g id="CARRIER">%s</xliff:g> ಮೂಲಕ ಡೇಟಾ ಅಥವಾ ಇಂಟರ್ನೆಟ್ಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುವುದಿಲ್ಲ. ಇಂಟರ್ನೆಟ್, ವೈ-ಫೈ ಮೂಲಕ ಮಾತ್ರ ಲಭ್ಯವಿರುತ್ತದೆ."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ನಿಮ್ಮ ವಾಹಕ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ, ಸೆಟ್ಟಿಂಗ್ಗಳಿಗೆ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ಮ್ಯಾಗ್ನಿಫೈರ್ ವಿಂಡೋ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ಈ ಬಟನ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ ಅಥವಾ ಬದಲಾಯಿಸಿ.\n\n"<annotation id="link">"ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ಅದನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಮರೆಮಾಡಲು ಅಂಚಿಗೆ ಬಟನ್ ಸರಿಸಿ"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ಎಡ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ಬಲ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ಸ್ಕ್ರೀನ್ನ ಎಡ ಕೆಳಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ಅಂಚಿಗೆ ಸರಿಸಿ ಮತ್ತು ಮರೆಮಾಡಿ"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ಅಂಚನ್ನು ಸರಿಸಿ ಮತ್ತು ತೋರಿಸಿ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ಟಾಗಲ್ ಮಾಡಿ"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳು"</string> <string name="controls_providers_title" msgid="6879775889857085056">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ಮೊಬೈಲ್ ಡೇಟಾ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗುವುದಿಲ್ಲ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ಯಾವುದೇ ಕನೆಕ್ಷನ್ ಇಲ್ಲ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ಇತರ ಯಾವುದೇ ನೆಟ್ವರ್ಕ್ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 11da089fe626..783365ce5478 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"사용자 설정"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"완료"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"닫기"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"연결됨"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"모바일 데이터를 사용 중지하시겠습니까?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>을(를) 통해 데이터 또는 인터넷에 액세스할 수 없게 됩니다. 인터넷은 Wi-Fi를 통해서만 사용할 수 있습니다."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"이동통신사"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"앱이 권한 요청을 가리고 있기 때문에 설정에서 내 응답을 확인할 수 없습니다."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"돋보기 창 설정"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"접근성 기능을 열려면 탭하세요. 설정에서 이 버튼을 맞춤설정하거나 교체할 수 있습니다.\n\n"<annotation id="link">"설정 보기"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"버튼을 가장자리로 옮겨서 일시적으로 숨기세요."</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"왼쪽 상단으로 이동"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"오른쪽 상단으로 이동"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"왼쪽 하단으로 이동"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"오른쪽 하단으로 이동"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"가장자리로 옮겨서 숨기기"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"가장자리 바깥으로 옮겨서 표시"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"전환"</string> <string name="quick_controls_title" msgid="6839108006171302273">"기기 컨트롤"</string> <string name="controls_providers_title" msgid="6879775889857085056">"컨트롤을 추가할 앱을 선택하세요"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"모바일 데이터"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"연결됨"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"모바일 데이터가 자동으로 연결되지 않음"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"연결되지 않음"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"사용 가능한 다른 네트워크가 없음"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 676076cdfb1e..b9da9f09d302 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстөрдү инверсиялоо"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түстөрдү тууралоо"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Колдонуучунун параметрлери"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Бүттү"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Жабуу"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Туташкан"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобилдик Интернетти өчүрөсүзбү?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> байланыш оператору аркылуу Интернетке кире албай каласыз. Интернетке Wi-Fi аркылуу гана кирүүгө болот."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"байланыш операторуңуз"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Уруксат берүү сурамыңыз көрүнбөй калгандыктан, Жөндөөлөр жообуңузду ырастай албай жатат."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Чоңойткуч терезесинин параметрлери"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Атайын мүмкүнчүлүктөрдү ачуу үчүн басыңыз. Бул баскычты Жөндөөлөрдөн өзгөртүңүз.\n\n"<annotation id="link">"Жөндөөлөрдү көрүү"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Баскычты убактылуу жашыра туруу үчүн экрандын четине жылдырыңыз"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жогорку сол жакка жылдыруу"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Жогорку оң жакка жылдырыңыз"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Төмөнкү сол жакка жылдыруу"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Төмөнкү оң жакка жылдырыңыз"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ичине жылдырып, көрсөтүңүз"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Сыртка жылдырып, көрсөтүңүз"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"өчүрүү/күйгүзүү"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Түзмөктү башкаруу элементтери"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Башкаруу элементтери кошула турган колдонмону тандоо"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилдик трафик"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Туташты"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилдик трафик автоматтык түрдө туташтырылбайт"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыш жок"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Башка тармактар жеткиликсиз"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index 4fc25e27b7f5..d7dbb86e022f 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ຕັ້ງຄ່າຜູ້ໃຊ້"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ແລ້ວໆ"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ປິດ"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"ເຊື່ອມຕໍ່ແລ້ວ"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ປິດອິນເຕີເນັດມືຖືໄວ້ບໍ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"ທ່ານຈະບໍ່ມີສິດເຂົ້າເຖິງຂໍ້ມູນ ຫຼື ອິນເຕີເນັດຜ່ານ <xliff:g id="CARRIER">%s</xliff:g>. ອິນເຕີເນັດຈະສາມາດໃຊ້ໄດ້ຜ່ານ Wi-Fi ເທົ່ານັ້ນ."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ຜູ້ໃຫ້ບໍລິການຂອງທ່ານ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ເນື່ອງຈາກມີແອັບໃດໜຶ່ງກຳລັງຂັດຂວາງການຂໍອະນຸຍາດ, ການຕັ້ງຄ່າຈຶ່ງບໍ່ສາມາດຢັ້ງຢືນການຕອບຮັບຂອງທ່ານໄດ້."</string> <string name="slice_permission_title" msgid="3262615140094151017">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ການຕັ້ງຄ່າໜ້າຈໍຂະຫຍາຍ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ແຕະເພື່ອເປີດຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ປັບແຕ່ງ ຫຼື ປ່ຽນປຸ່ມນີ້ໃນການຕັ້ງຄ່າ.\n\n"<annotation id="link">"ເບິ່ງການຕັ້ງຄ່າ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ຍ້າຍປຸ່ມໄປໃສ່ຂອບເພື່ອເຊື່ອງມັນຊົ່ວຄາວ"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ຍ້າຍຊ້າຍເທິງ"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ຍ້າຍຂວາເທິງ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ຍ້າຍຊ້າຍລຸ່ມ"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ຍ້າຍຂວາລຸ່ມ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ຍ້າຍອອກຂອບ ແລະ ເຊື່ອງ"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ຍ້າຍອອກຂອບ ແລະ ສະແດງ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ສະຫຼັບ"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ການຄວບຄຸມອຸປະກອນ"</string> <string name="controls_providers_title" msgid="6879775889857085056">"ເລືອກແອັບເພື່ອເພີ່ມການຄວບຄຸມ"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ອິນເຕີເນັດມືຖື"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"ເຊື່ອມຕໍ່ແລ້ວ"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ຈະບໍ່ເຊື່ອມຕໍ່ອິນເຕີເນັດມືຖືອັດຕະໂນມັດ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ບໍ່ມີການເຊື່ອມຕໍ່"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ບໍ່ມີເຄືອຂ່າຍອື່ນທີ່ສາມາດໃຊ້ໄດ້"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index b9d6f33c967c..e631ce2a389d 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Naudotojo nustatymai"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Atlikta"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Uždaryti"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Prijungtas"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Išjungti mobiliojo ryšio duomenis?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Naudodamiesi „<xliff:g id="CARRIER">%s</xliff:g>“ paslaugomis neturėsite galimybės pasiekti duomenų arba interneto. Internetą galėsite naudoti tik prisijungę prie „Wi-Fi“."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"savo operatoriaus"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Kadangi programa užstoja leidimo užklausą, nustatymuose negalima patvirtinti jūsų atsakymo."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Didinimo lango nustatymai"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Palietę atidarykite pritaikymo neįgaliesiems funkcijas. Tinkinkite arba pakeiskite šį mygtuką nustatymuose.\n\n"<annotation id="link">"Žr. nustatymus"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Perkelkite mygtuką prie krašto, kad laikinai jį paslėptumėte"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Perkelti į viršų kairėje"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Perkelti į viršų dešinėje"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Perkelti į apačią kairėje"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Perkelti į apačią dešinėje"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Perkelti į kraštą ir slėpti"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Perkelti iš krašto ir rodyti"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"perjungti"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Įrenginio valdikliai"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Pasirinkite programą, kad pridėtumėte valdiklių"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiliojo ryšio duomenys"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Prisijungta"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Naud. mob. r. duomenis nebus autom. prisijungiama"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nėra ryšio"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nėra kitų pasiekiamų tinklų"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 882ff7c8ae43..0a08654eb2e4 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Lietotāja iestatījumi"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Gatavs"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Aizvērt"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Pievienota"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vai izslēgt mobilos datus?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Izmantojot mobilo sakaru operatora <xliff:g id="CARRIER">%s</xliff:g> pakalpojumus, nevarēsiet piekļūt datiem vai internetam. Internetam varēsiet piekļūt, tikai izmantojot Wi-Fi savienojumu."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jūsu mobilo sakaru operators"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Lietotne Iestatījumi nevar verificēt jūsu atbildi, jo cita lietotne aizsedz atļaujas pieprasījumu."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Lupas loga iestatījumi"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atveriet pieejamības funkcijas. Pielāgojiet vai aizstājiet šo pogu iestatījumos.\n\n"<annotation id="link">"Skatīt iestatījumus"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Lai īslaicīgi paslēptu pogu, pārvietojiet to uz malu"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pārvietot augšpusē pa kreisi"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pārvietot augšpusē pa labi"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pārvietot apakšpusē pa kreisi"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pārvietot apakšpusē pa labi"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pārvietot uz malu un paslēpt"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pārvietot no malas un parādīt"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"pārslēgt"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Ierīču vadīklas"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Izvēlieties lietotni, lai pievienotu vadīklas"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilie dati"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Ir izveidots savienojums"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilo datu savienojums netiks veidots automātiski"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nav savienojuma"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nav pieejams neviens cits tīkls"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 0d98dc60477f..e714055585a9 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Кориснички поставки"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затвори"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Поврзано"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се исклучи мобилниот интернет?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Нема да имате пристап до податоците или интернетот преку <xliff:g id="CARRIER">%s</xliff:g>. Интернетот ќе биде достапен само преку Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"вашиот оператор"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Бидејќи апликацијата го прикрива барањето за дозвола, „Поставките“ не може да го потврдат вашиот одговор."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Поставки за прозорец за лупа"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Допрете за функциите за пристапност. Приспособете или заменете го копчево во „Поставки“.\n\n"<annotation id="link">"Прикажи поставки"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете го копчето до работ за да го сокриете привремено"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Премести горе десно"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Премести долу лево"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Премести долу десно"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Премести до работ и сокриј"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Премести над работ и прикажи"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"вклучување/исклучување"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Контроли за уредите"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Изберете апликација за да додадете контроли"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилен интернет"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Поврзано"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилниот интернет не може да се поврзе автоматски"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Нема интернет-врска"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нема други достапни мрежи"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 868d75989943..666e60134d46 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ഉപയോക്തൃ ക്രമീകരണം"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ഉപയോക്താക്കളെ മാനേജ് ചെയ്യുക"</string> <string name="quick_settings_done" msgid="2163641301648855793">"പൂർത്തിയാക്കി"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"അടയ്ക്കുക"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"കണക്റ്റുചെയ്തു"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"മൊബൈൽ ഡാറ്റ ഓഫാക്കണോ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"നിങ്ങൾക്ക് ഡാറ്റയിലേക്ക് ആക്സസോ അല്ലെങ്കിൽ <xliff:g id="CARRIER">%s</xliff:g> മുഖേനയുള്ള ഇന്റർനെറ്റോ ഉണ്ടാകില്ല. വൈഫൈ മുഖേന മാത്രമായിരിക്കും ഇന്റർനെറ്റ് ലഭ്യത."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"നിങ്ങളുടെ കാരിയർ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"അനുമതി അഭ്യർത്ഥനയെ ഒരു ആപ്പ് മറയ്ക്കുന്നതിനാൽ, ക്രമീകരണത്തിന് നിങ്ങളുടെ പ്രതികരണം പരിശോധിച്ചുറപ്പിക്കാനാകില്ല."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"മാഗ്നിഫയർ വിൻഡോ ക്രമീകരണം"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ഉപയോഗസഹായി ഫീച്ചർ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ക്രമീകരണത്തിൽ ഈ ബട്ടൺ ഇഷ്ടാനുസൃതമാക്കാം, മാറ്റാം.\n\n"<annotation id="link">"ക്രമീകരണം കാണൂ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"തൽക്കാലം മറയ്ക്കുന്നതിന് ബട്ടൺ അരുകിലേക്ക് നീക്കുക"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"മുകളിൽ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"മുകളിൽ വലതുഭാഗത്തേക്ക് നീക്കുക"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ചുവടെ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"എഡ്ജിലേക്ക് നീക്കി മറയ്ക്കുക"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"എഡ്ജിൽ നിന്ന് നീക്കി കാണിക്കൂ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"മാറ്റുക"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ഉപകരണ നിയന്ത്രണങ്ങൾ"</string> <string name="controls_providers_title" msgid="6879775889857085056">"നിയന്ത്രണങ്ങൾ ചേർക്കാൻ ആപ്പ് തിരഞ്ഞെടുക്കുക"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"മൊബൈൽ ഡാറ്റ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"കണക്റ്റ് ചെയ്തു"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"മൊബൈൽ ഡാറ്റ സ്വയം കണക്റ്റ് ചെയ്യില്ല"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"കണക്ഷനില്ല"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"മറ്റ് നെറ്റ്വർക്കുകളൊന്നും ലഭ്യമല്ല"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 25929e6c2676..769cdda78337 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө хувиргалт"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгө тохируулга"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Хэрэглэгчийн тохиргоо"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Хэрэглэгчдийг удирдах"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Дууссан"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Хаах"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Холбогдсон"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобайл датаг унтраах уу?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Та <xliff:g id="CARRIER">%s</xliff:g>-р дата эсвэл интернэтэд хандах боломжгүй болно. Интернэтэд зөвхөн Wi-Fi-р холбогдох боломжтой болно."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"таны оператор компани"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Апп нь зөвшөөрлийн хүсэлтийг танихгүй байгаа тул Тохиргооноос таны хариултыг баталгаажуулах боломжгүй байна."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулахыг зөвшөөрөх үү?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Энэ нь <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Томруулагчийн цонхны тохиргоо"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Хандалтын онцлогуудыг нээхийн тулд товшино уу. Энэ товчлуурыг Тохиргоо хэсэгт өөрчилж эсвэл солиорой.\n\n"<annotation id="link">"Тохиргоог харах"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Үүнийг түр нуухын тулд товчлуурыг зах руу зөөнө үү"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Зүүн дээш зөөх"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Баруун дээш зөөх"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Зүүн доош зөөх"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Баруун доош зөөх"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ирмэг рүү зөөж, нуух"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Ирмэгээс гаргаж, харуулах"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"асаах/унтраах"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Төхөөрөмжийн хяналт"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Хяналтууд нэмэхийн тулд аппыг сонгоно уу"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобайл дата"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Холбогдсон"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобайл дата автоматаар холбогдохгүй"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Холболт алга"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Өөр боломжтой сүлжээ байхгүй байна"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index c6e99a74518e..d45b1e599cee 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"वापरकर्ता सेटिंग्ज"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"पूर्ण झाले"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"बंद करा"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"कनेक्ट केलेले"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करायचा?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"तुम्हाला <xliff:g id="CARRIER">%s</xliff:g> मधून डेटा किंवा इंटरनेटचा अॅक्सेस नसेल. इंटरनेट फक्त वाय-फाय मार्फत उपलब्ध असेल."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तुमचा वाहक"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"अॅप परवानगी विनंती अस्पष्ट करत असल्याने, सेटिंग्ज तुमचा प्रतिसाद पडताळू शकत नाहीत."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"मॅग्निफायर विंडो सेटिंग्ज"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"अॅक्सेसिबिलिटी वैशिष्ट्ये उघडण्यासाठी, टॅप करा. सेटिंग्जमध्ये हे बटण कस्टमाइझ करा किंवा बदला.\n\n"<annotation id="link">"सेटिंग्ज पहा"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटण तात्पुरते लपवण्यासाठी ते कोपर्यामध्ये हलवा"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"वर डावीकडे हलवा"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"वर उजवीकडे हलवा"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"तळाशी डावीकडे हलवा"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"तळाशी उजवीकडे हलवा"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"एजवर हलवा आणि लपवा"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"एजवर हलवा आणि दाखवा"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टॉगल करा"</string> <string name="quick_controls_title" msgid="6839108006171302273">"डिव्हाइस नियंत्रणे"</string> <string name="controls_providers_title" msgid="6879775889857085056">"नियंत्रणे जोडण्यासाठी ॲप निवडा"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट केले आहे"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा ऑटो-कनेक्ट होणार नाही"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"कोणतेही कनेक्शन नाही"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"इतर कोणतेही नेटवर्क उपलब्ध नाहीत"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 99349b157f18..9e43396fcd08 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Tetapan pengguna"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tutup"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Disambungkan"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Matikan data mudah alih?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan mempunyai akses kepada data atau Internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya tersedia melaui Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"pembawa anda"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Oleh sebab apl melindungi permintaan kebenaran, Tetapan tidak dapat mengesahkan jawapan anda."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Tetapan tetingkap penggadang"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketik untuk membuka ciri kebolehaksesan. Sesuaikan/gantikan butang ini dalam Tetapan.\n\n"<annotation id="link">"Lihat tetapan"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Gerakkan butang ke tepi untuk disembunyikan buat sementara waktu"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Alihkan ke atas sebelah kiri"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Alihkan ke atas sebelah kanan"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Alihkan ke bawah sebelah kiri"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Alihkan ke bawah sebelah kanan"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Alihkan ke tepi dan sorokkan"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alihkan ke tepi dan tunjukkan"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"togol"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kawalan peranti"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Data mudah alih"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Disambungkan"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Data mudah alih tidak akan autosambung"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Tiada sambungan"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Tiada rangkaian lain yang tersedia"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index f30846ac1853..8f404b85639f 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"အသုံးပြုသူ ဆက်တင်များ"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ပြီးပါပြီ"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ပိတ်ရန်"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"ချိတ်ဆက်ထား"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"မိုဘိုင်းဒေတာ ပိတ်မလား။"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> မှတစ်ဆင့် ဒေတာ သို့မဟုတ် အင်တာနက်ကို မသုံးနိုင်ပါ။ Wi-Fi ဖြင့်သာ အင်တာနက် သုံးနိုင်သည်။"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"သင်၏ ဝန်ဆောင်မှုပေးသူ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"အပလီကေးရှင်းတစ်ခုက ခွင့်ပြုချက်တောင်းခံမှုကို ပိတ်ထားသောကြောင့် ဆက်တင်များသည် သင်၏ လုပ်ဆောင်ကို တုံ့ပြန်နိုင်ခြင်းမရှိပါ။"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"မှန်ဘီလူးဝင်းဒိုး ဆက်တင်များ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ရန် တို့ပါ။ ဆက်တင်များတွင် ဤခလုတ်ကို စိတ်ကြိုက်ပြင်ပါ (သို့) လဲပါ။\n\n"<annotation id="link">"ဆက်တင်များ ကြည့်ရန်"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ခလုတ်ကို ယာယီဝှက်ရန် အစွန်းသို့ရွှေ့ပါ"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ဘယ်ဘက်ထိပ်သို့ ရွှေ့ရန်"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ညာဘက်ထိပ်သို့ ရွှေ့ရန်"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ဘယ်ဘက်အောက်ခြေသို့ ရွှေ့ရန်"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ညာဘက်အောက်ခြေသို့ ရွှေ့ရန်"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"အစွန်းသို့ရွှေ့ပြီး ဝှက်ရန်"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"အစွန်းမှရွှေ့ပြီး ပြရန်"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ပြောင်းရန်"</string> <string name="quick_controls_title" msgid="6839108006171302273">"စက်ထိန်းစနစ်"</string> <string name="controls_providers_title" msgid="6879775889857085056">"ထိန်းချုပ်မှုများထည့်ရန် အက်ပ်ရွေးခြင်း"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"မိုဘိုင်းဒေတာ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"ချိတ်ဆက်ထားသည်"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"မိုဘိုင်းဒေတာ အော်တိုမချိတ်ပါ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ချိတ်ဆက်မှုမရှိပါ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"အခြားကွန်ရက်များ မရှိပါ"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 117c8645ddc1..94b8e9049afb 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brukerinnstillinger"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Ferdig"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Lukk"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Tilkoblet"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du slå av mobildata?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du får ikke tilgang til data eller internett via <xliff:g id="CARRIER">%s</xliff:g>. Internett er bare tilgjengelig via Wifi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatøren din"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Fordi en app skjuler tillatelsesforespørselen, kan ikke Innstillinger bekrefte svaret ditt."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Innstillinger for forstørringsvindu"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trykk for å åpne tilgj.funksjoner. Tilpass eller bytt knappen i Innstillinger.\n\n"<annotation id="link">"Se innstillingene"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytt knappen til kanten for å skjule den midlertidig"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytt til øverst til venstre"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flytt til øverst til høyre"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flytt til nederst til venstre"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flytt til nederst til høyre"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flytt til kanten og skjul"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flytt ut kanten og vis"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"slå av/på"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyring"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Velg en app for å legge til kontroller"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Tilkoblet"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobildata kobler ikke til automatisk"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen tilkobling"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ingen andre nettverk er tilgjengelige"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 351971594b62..35937a9114f0 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"कलर करेक्सन"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"प्रयोगकर्तासम्बन्धी सेटिङ"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"भयो"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"बन्द गर्नुहोस्"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"जोडिएको"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा निष्क्रिय पार्ने हो?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"तपाईं <xliff:g id="CARRIER">%s</xliff:g> मार्फत डेटा वा इन्टरनेट प्रयोग गर्न सक्नुहुने छैन। Wi-Fi मार्फत मात्र इन्टरनेट उपलब्ध हुने छ।"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तपाईंको सेवा प्रदायक"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"कुनै एपको कारणले अनुमतिसम्बन्धी अनुरोध बुझ्न गाह्रो भइरहेकोले सेटिङहरूले तपाईंको प्रतिक्रिया प्रमाणित गर्न सक्दैनन्।"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- यसले <xliff:g id="APP">%1$s</xliff:g> बाट जानकारी पढ्न सक्छ"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"म्याग्निफायर विन्डोसम्बन्धी सेटिङ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सर्वसुलभता कायम गर्ने सुविधा खोल्न ट्याप गर्नुहोस्। सेटिङमा गई यो बटन कस्टमाइज गर्नुहोस् वा बदल्नुहोस्।\n\n"<annotation id="link">"सेटिङ हेर्नुहोस्"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"यो बटन केही बेर नदेखिने पार्न किनारातिर सार्नुहोस्"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सिरानको बायाँतिर सार्नुहोस्"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"सिरानको दायाँतिर सार्नुहोस्"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"पुछारको बायाँतिर सार्नुहोस्"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"पुछारको दायाँतिर सार्नुहोस्"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"किनारामा सार्नुहोस् र नदेखिने पार्नु…"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"किनाराबाट सार्नुहोस् र देखिने पार्नु…"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टगल गर्नुहोस्"</string> <string name="quick_controls_title" msgid="6839108006171302273">"डिभाइस नियन्त्रण गर्ने विजेटहरू"</string> <string name="controls_providers_title" msgid="6879775889857085056">"कन्ट्रोल थप्नु पर्ने एप छान्नुहोस्"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"इन्टरनेटमा कनेक्ट गरिएको छ"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा स्वतः कनेक्ट हुँदैन"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"इन्टरनेट छैन"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"अन्य नेटवर्क उपलब्ध छैनन्"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 3c85148d8d1a..4de03314cec5 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikersinstellingen"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sluiten"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Verbonden"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobiele data uitzetten?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Je hebt dan geen toegang meer tot data of internet via <xliff:g id="CARRIER">%s</xliff:g>. Internet is alleen nog beschikbaar via wifi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"je provider"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Aangezien een app een rechtenverzoek afdekt, kan Instellingen je reactie niet verifiëren."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Instellingen voor vergrotingsvenster"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik voor toegankelijkheidsfuncties. Wijzig of vervang deze knop via Instellingen.\n\n"<annotation id="link">"Naar Instellingen"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Knop naar de rand verplaatsen om deze tijdelijk te verbergen"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Naar linksboven verplaatsen"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Naar rechtsboven verplaatsen"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Naar linksonder verplaatsen"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Naar rechtsonder verplaatsen"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Naar rand verplaatsen en verbergen"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Over rand verplaatsen en tonen"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"schakelen"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Apparaatbediening"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Kies de app waaraan je bedieningselementen wilt toevoegen"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Verbonden"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data maakt niet automatisch verbinding"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen andere netwerken beschikbaar"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index 1cb3436d95e3..16941b86f4c0 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ରଙ୍ଗ ଇନଭାର୍ସନ"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ଉପଯୋଗକର୍ତ୍ତା ସେଟିଂସ"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ହୋଇଗଲା"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ବନ୍ଦ କରନ୍ତୁ"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"ସଂଯୁକ୍ତ"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ମୋବାଇଲ୍ ଡାଟା ବନ୍ଦ କରିବେ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"ଡାଟା କିମ୍ବା ଇଣ୍ଟରନେଟ୍କୁ <xliff:g id="CARRIER">%s</xliff:g> ଦ୍ଵାରା ଆପଣଙ୍କର ଆକ୍ସେସ୍ ରହିବ ନାହିଁ। ଇଣ୍ଟରନେଟ୍ କେବଳ ୱାଇ-ଫାଇ ମାଧ୍ୟମରେ ଉପଲବ୍ଧ ହେବ।"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ଆପଣଙ୍କ କେରିଅର୍"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ଗୋଟିଏ ଆପ୍ ଏକ ଅନୁମତି ଅନୁରୋଧକୁ ଦେଖିବାରେ ବାଧା ଦେଉଥିବାରୁ, ସେଟିଙ୍ଗ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରିପାରିବ ନାହିଁ।"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ମ୍ୟାଗ୍ନିଫାୟର ୱିଣ୍ଡୋର ସେଟିଂସ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚର ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ। ସେଟିଂସରେ ଏହି ବଟନକୁ କଷ୍ଟମାଇଜ କର କିମ୍ବା ବଦଳାଅ।\n\n"<annotation id="link">"ସେଟିଂସ ଦେଖନ୍ତୁ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ବଟନକୁ ଅସ୍ଥାୟୀ ଭାବେ ଲୁଚାଇବା ପାଇଁ ଏହାକୁ ଗୋଟିଏ ଧାରକୁ ମୁଭ୍ କରନ୍ତୁ"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ଶୀର୍ଷ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ଶୀର୍ଷ ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ନିମ୍ନ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ନିମ୍ନ ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ଧାରକୁ ମୁଭ୍ କରି ଲୁଚାନ୍ତୁ"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ଧାର ବାହାରକୁ ମୁଭ୍ କରି ଦେଖାନ୍ତୁ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ଟୋଗଲ୍ କରନ୍ତୁ"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string> <string name="controls_providers_title" msgid="6879775889857085056">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଯୋଗ କରିବାକୁ ଆପ୍ ବାଛନ୍ତୁ"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ମୋବାଇଲ ଡାଟା"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"ସଂଯୋଗ କରାଯାଇଛି"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ମୋବାଇଲ ଡାଟା ସ୍ୱତଃ-ସଂଯୋଗ ହେବ ନାହିଁ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ସଂଯୋଗ ନାହିଁ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ଅନ୍ୟ କୌଣସି ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 992ffd6d2b5d..fce69ec0fa33 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ਵਰਤੋਂਕਾਰ ਸੈਟਿੰਗਾਂ"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ਹੋ ਗਿਆ"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ਬੰਦ ਕਰੋ"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"ਕਨੈਕਟ ਕੀਤਾ"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ਕੀ ਮੋਬਾਈਲ ਡਾਟਾ ਬੰਦ ਕਰਨਾ ਹੈ?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"ਤੁਸੀਂ <xliff:g id="CARRIER">%s</xliff:g> ਰਾਹੀਂ ਡਾਟੇ ਜਾਂ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕਰ ਸਕੋਗੇ। ਇੰਟਰਨੈੱਟ ਸਿਰਫ਼ ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਉਪਲਬਧ ਹੋਵੇਗਾ।"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ਤੁਹਾਡਾ ਕੈਰੀਅਰ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"ਕਿਸੇ ਐਪ ਵੱਲੋਂ ਇਜਾਜ਼ਤ ਬੇਨਤੀ ਨੂੰ ਢਕੇ ਜਾਣ ਕਾਰਨ ਸੈਟਿੰਗਾਂ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕਰ ਸਕਦੀਆਂ।"</string> <string name="slice_permission_title" msgid="3262615140094151017">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ਵੱਡਦਰਸ਼ੀ ਵਿੰਡੋ ਸੈਟਿੰਗਾਂ"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ। ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਹ ਬਟਨ ਵਿਉਂਤਬੱਧ ਕਰੋ ਜਾਂ ਬਦਲੋ।\n\n"<annotation id="link">"ਸੈਟਿੰਗਾਂ ਦੇਖੋ"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ਬਟਨ ਨੂੰ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਲੁਕਾਉਣ ਲਈ ਕਿਨਾਰੇ \'ਤੇ ਲਿਜਾਓ"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ਉੱਪਰ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ਉੱਪਰ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ਹੇਠਾਂ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ਕਿਨਾਰੇ ਵਿੱਚ ਲਿਜਾ ਕੇ ਲੁਕਾਓ"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ਕਿਨਾਰੇ ਤੋਂ ਬਾਹਰ ਕੱਢ ਕੇ ਦਿਖਾਓ"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ਟੌਗਲ ਕਰੋ"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ਡੀਵਾਈਸ ਕੰਟਰੋਲ"</string> <string name="controls_providers_title" msgid="6879775889857085056">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ਮੋਬਾਈਲ ਡਾਟਾ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"ਕਨੈਕਟ ਹੈ"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ਮੋਬਾਈਲ ਡਾਟਾ ਸਵੈ-ਕਨੈਕਟ ਨਹੀਂ ਹੋਵੇਗਾ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ਕੋਈ ਕਨੈਕਸ਼ਨ ਨਹੀਂ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ਕੋਈ ਹੋਰ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index b706359e657a..8787f365a565 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ustawienia użytkownika"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Gotowe"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zamknij"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Połączono"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Wyłączyć mobilną transmisję danych?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nie będziesz mieć dostępu do transmisji danych ani internetu w <xliff:g id="CARRIER">%s</xliff:g>. Internet będzie dostępny tylko przez Wi‑Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Twój operator"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikacja Ustawienia nie może zweryfikować Twojej odpowiedzi, ponieważ inna aplikacja zasłania prośbę o udzielenie uprawnień."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ustawienia okna powiększania"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Kliknij, aby otworzyć ułatwienia dostępu. Dostosuj lub zmień ten przycisk w Ustawieniach.\n\n"<annotation id="link">"Wyświetl ustawienia"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Przesuń przycisk do krawędzi, aby ukryć go tymczasowo"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Przenieś w lewy górny róg"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Przenieś w prawy górny róg"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Przenieś w lewy dolny róg"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Przenieś w prawy dolny róg"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Przenieś do krawędzi i ukryj"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Przenieś poza krawędź i pokaż"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"przełącz"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Sterowanie urządzeniami"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Wybierz aplikację, do której chcesz dodać elementy sterujące"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilna transmisja danych"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Połączono"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna transmisja danych nie połączy się automatycznie"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Brak połączenia"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Brak innych dostępnych sieci"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 78a6409e4c5e..e6f05806989a 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Config. do usuário"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configurações da janela de lupa"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover para o canto superior direito"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover para o canto inferior esquerdo"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover para o canto inferior direito"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover para a borda e ocultar"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover para fora da borda e exibir"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alternar"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 30233b3dec71..6c07ce0732b0 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção da cor"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Definições do utilizador"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gerir utilizadores"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Ligado"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Não terá acesso a dados ou à Internet através do operador <xliff:g id="CARRIER">%s</xliff:g>. A Internet estará disponível apenas por Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o seu operador"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Uma vez que uma app está a ocultar um pedido de autorização, as Definições não conseguem validar a sua resposta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Definições da janela da lupa"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir funcionalidades de acessibilidade. Personal. ou substitua botão em Defin.\n\n"<annotation id="link">"Ver defin."</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a extremidade para o ocultar temporariamente"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover p/ parte sup. esquerda"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover parte superior direita"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover p/ parte infer. esquerda"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover parte inferior direita"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover p/ extremidade e ocultar"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Retirar extremidade e mostrar"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ativar/desativar"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Controlos de dispositivos"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Escolha uma app para adicionar controlos"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Ligado"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem ligação automática com dados móveis"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem ligação"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 78a6409e4c5e..e6f05806989a 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Config. do usuário"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configurações da janela de lupa"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover para o canto superior direito"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover para o canto inferior esquerdo"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover para o canto inferior direito"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover para a borda e ocultar"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover para fora da borda e exibir"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alternar"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 0d960626a397..080edfda3271 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setări de utilizator"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închide"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Conectat"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivezi datele mobile?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu vei avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul tău"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu îți pot verifica răspunsul."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Permiți ca <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setările ferestrei de mărire"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atinge ca să deschizi funcțiile de accesibilitate. Personalizează sau înlocuiește butonul în setări.\n\n"<annotation id="link">"Vezi setările"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mută în dreapta sus"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mută în stânga jos"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mută în dreapta jos"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mută la margine și ascunde"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mută de la margine și afișează"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Activează / dezactivează"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Date mobile"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectat"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nu se conectează automat la date mobile"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nicio conexiune"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nu sunt disponibile alte rețele"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index 3d13b2cc088b..b42701ef102e 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пользовательские настройки"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрыть"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Подключено"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Отключить мобильный Интернет?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Вы не сможете передавать данные или выходить в Интернет через оператора \"<xliff:g id="CARRIER">%s</xliff:g>\". Интернет будет доступен только по сети Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Невозможно принять ваше согласие, поскольку запрос скрыт другим приложением."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Настройка окна лупы"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Нажмите, чтобы открыть спец. возможности. Настройте или замените эту кнопку в настройках.\n\n"<annotation id="link">"Настройки"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Чтобы временно скрыть кнопку, переместите ее к краю экрана"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перенести в левый верхний угол"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перенести в правый верхний угол"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перенести в левый нижний угол"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перенести в правый нижний угол"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перенести к краю и скрыть"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Вернуть из-за края и показать"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"включить или отключить"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Управление устройствами"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Чтобы добавить виджеты управления, выберите приложение"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильный интернет"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Подключено"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Без автоподключения к мобильному интернету"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Нет подключения к интернету"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нет других доступных сетей"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 01be742baee7..bd272c7386a0 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"පරිශීලක සැකසීම්"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"නිමයි"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"වසන්න"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"සම්බන්ධිත"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ජංගම දත්ත ක්රියාවිරහිත කරන්නද?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"ඔබට <xliff:g id="CARRIER">%s</xliff:g> හරහා දත්ත හෝ අන්තර්ජාලයට පිවිසීමේ හැකියාවක් නැත. අන්තර්ජාලය Wi-Fi හරහා පමණක් ලබා ගත හැකිය."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ඔබගේ වාහකය"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"යෙදුමක් අවසර ඉල්ලීමක් කරන නිසා, සැකසීම්වලට ඔබගේ ප්රතිචාරය සත්යාපනය කළ නොහැකිය."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"විශාලන කවුළු සැකසීම්"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ප්රවේශ්යතා විශේෂාංග විවෘත කිරීමට තට්ටු කරන්න. සැකසීම් තුළ මෙම බොත්තම අභිරුචිකරණය හෝ ප්රතිස්ථාපනය කරන්න.\n\n"<annotation id="link">"සැකසීම් බලන්න"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"එය තාවකාලිකව සැඟවීමට බොත්තම දාරයට ගෙන යන්න"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ඉහළ වමට ගෙන යන්න"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ඉහළ දකුණට ගෙන යන්න"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"පහළ වමට ගෙන යන්න"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"පහළ දකුණට ගෙන යන්න"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"මායිමට ගෙන යන්න සහ සඟවන්න"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"මායිමෙන් පිටට ගන්න සහ පෙන්වන්න"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ටොගල් කරන්න"</string> <string name="quick_controls_title" msgid="6839108006171302273">"උපාංග පාලන"</string> <string name="controls_providers_title" msgid="6879775889857085056">"පාලන එක් කිරීමට යෙදුම තෝරා ගන්න"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"ජංගම දත්ත"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"සම්බන්ධයි"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"ජංගම දත්ත ස්වංක්රියව සම්බන්ධ නොවනු ඇත"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"සම්බන්ධතාවයක් නැත"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ලබා ගත හැකි වෙනත් ජාල නැත"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 76dce7465e1e..a0dd3c375485 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzia farieb"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Úprava farieb"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Používateľské nastavenia"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zavrieť"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Pripojené"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Chcete vypnúť mobilné dáta?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nebudete mať prístup k dátam ani internetu prostredníctvom operátora <xliff:g id="CARRIER">%s</xliff:g>. Internet bude k dispozícii iba cez Wi‑Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"váš operátor"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Nastavenia nemôžu overiť vašu odpoveď, pretože určitá aplikácia blokuje žiadosť o povolenie."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavenia okna lupy"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Funkcie dostupnosti otvoríte klepnutím. Tlačidlo prispôsobte alebo nahraďte v Nastav.\n\n"<annotation id="link">"Zobraz. nast."</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ak chcete tlačidlo dočasne skryť, presuňte ho k okraju"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Presunúť doľava nahor"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Presunúť doprava nahor"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Presunúť doľava nadol"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Presunúť doprava nadol"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Presunúť k okraju a skryť"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Presunúť z okraja a zobraziť"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"prepínač"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Ovládanie zariadení"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikáciu, ktorej ovládače si chcete pridať"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilné dáta"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Pripojené"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Automatické pripojenie cez mobilné dáta nefunguje"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Bez pripojenia"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nie sú k dispozícii žiadne ďalšie siete"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index cb306c2dc315..44108c3279bc 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svetlost"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija barv"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Popravljanje barv"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Uporabniške nastavitve"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Končano"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zapri"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Povezava je vzpostavljena"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite izklopiti prenos podatkov v mobilnih omrežjih?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Prek operaterja »<xliff:g id="CARRIER">%s</xliff:g>« ne boste imeli dostopa do podatkovne povezave ali interneta. Internet bo na voljo samo prek povezave Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"svojega operaterja"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Ker aplikacija zakriva zahtevo za dovoljenje, z nastavitvami ni mogoče preveriti vašega odziva."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Želite dovoliti, da aplikacija <xliff:g id="APP_0">%1$s</xliff:g> prikaže izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna povečevalnika"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dotaknite se za funkcije za ljudi s posebnimi potrebami. Ta gumb lahko prilagodite ali zamenjate v nastavitvah.\n\n"<annotation id="link">"Ogled nastavitev"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Če želite gumb začasno skriti, ga premaknite ob rob."</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premakni zgoraj levo"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premakni zgoraj desno"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premakni spodaj levo"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premakni spodaj desno"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premakni na rob in skrij"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Premakni z roba in pokaži"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"preklop"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kontrolniki naprave"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Izberite aplikacijo za dodajanje kontrolnikov"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Prenos podatkov v mobilnem omrežju"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna podatkovna povezava ne bo samodejna."</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ni povezave"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nobeno drugo omrežje ni na voljo"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 9a6d73c07008..7630261585f8 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cilësimet e përdoruesit"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"U krye"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Mbyll"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"I lidhur"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Të çaktivizohen të dhënat celulare?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nuk do të kesh qasje te të dhënat ose interneti nëpërmjet <xliff:g id="CARRIER">%s</xliff:g>. Interneti do të ofrohet vetëm nëpërmjet Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatori yt celular"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Duke qenë se një aplikacion po bllokon një kërkesë për leje, \"Cilësimet\" nuk mund të verifikojnë përgjigjen tënde."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Cilësimet e dritares së zmadhimit"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trokit dhe hap veçoritë e qasshmërisë. Modifiko ose ndërro butonin te \"Cilësimet\".\n\n"<annotation id="link">"Shih cilësimet"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Zhvendose butonin në skaj për ta fshehur përkohësisht"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Zhvendos lart majtas"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Zhvendos lart djathtas"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Zhvendos poshtë majtas"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Zhvendos poshtë djathtas"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Zhvendose te skaji dhe fshihe"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Zhvendose jashtë skajit dhe shfaqe"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktivizo/çaktivizo"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Kontrollet e pajisjes"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Zgjidh aplikacionin për të shtuar kontrollet"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Të dhënat celulare"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Lidhur"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Të dhënat celulare nuk do të lidhen automatikisht"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nuk ka lidhje"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nuk ofrohet asnjë rrjet tjetër"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 0dff11f87eb6..2a7b88f42a08 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Корисничка подешавања"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затвори"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Повезан"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Желите да искључите мобилне податке?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Нећете имати приступ подацима или интернету преко мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>. Интернет ће бити доступан само преко WiFi везе."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"мобилни оператер"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Подешавања не могу да верификују ваш одговор јер апликација скрива захтев за дозволу."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Подешавања прозора за увећање"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Додирните за функције приступачности. Прилагодите или замените ово дугме у Подешавањима.\n\n"<annotation id="link">"Подешавања"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Померите дугме до ивице да бисте га привремено сакрили"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Премести горе десно"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Премести доле лево"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Премести доле десно"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Премести до ивице и сакриј"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Премести изван ивице и прикажи"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"укључите/искључите"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Контроле уређаја"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Одаберите апликацију за додавање контрола"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни подаци"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Повезано"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Није успело аутом. повезивање преко моб. података"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Веза није успостављена"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Није доступна ниједна друга мрежа"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index c1cdc5b5381c..48654d515882 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Användarinställningar"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Klart"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Stäng"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Ansluten"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vill du inaktivera mobildata?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kan inte skicka data eller använda internet via <xliff:g id="CARRIER">%s</xliff:g>. Internetanslutning blir bara möjlig via wifi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"din operatör"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Svaret kan inte verifieras av Inställningar eftersom en app skymmer en begäran om behörighet."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Inställningar för förstoringsfönster"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryck för att öppna tillgänglighetsfunktioner. Anpassa/ersätt knappen i Inställningar.\n\n"<annotation id="link">"Inställningar"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytta knappen till kanten för att dölja den tillfälligt"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytta högst upp till vänster"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flytta högst upp till höger"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flytta längst ned till vänster"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flytta längst ned till höger"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flytta till kanten och dölj"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flytta från kanten och visa"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktivera och inaktivera"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyrning"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Välj en app om du vill lägga till snabbkontroller"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Ansluten"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Du ansluts inte till mobildata automatiskt"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen anslutning"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Inga andra nätverk är tillgängliga"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 8ed824e418b8..6bf2bc2f54d4 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mipangilio ya mtumiaji"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Nimemaliza"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Funga"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Imeunganishwa"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Ungependa kuzima data ya mtandao wa simu?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Hutaweza kufikia data au intaneti kupitia <xliff:g id="CARRIER">%s</xliff:g>. Intaneti itapatikana kupitia Wi-Fi pekee."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mtoa huduma wako"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Kwa sababu programu nyingine inazuia ombi la ruhusa, hatuwezi kuthibitisha jibu lako katika Mipangilio."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Mipangilio ya dirisha la kikuzaji"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Gusa ili ufungue vipengele vya ufikivu. Weka mapendeleo au ubadilishe kitufe katika Mipangilio.\n\n"<annotation id="link">"Angalia mipangilio"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sogeza kitufe kwenye ukingo ili ukifiche kwa muda"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sogeza juu kushoto"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sogeza juu kulia"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sogeza chini kushoto"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sogeza chini kulia"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sogeza kwenye ukingo kisha ufiche"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sogeza nje ya ukingo kisha uonyeshe"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"geuza"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Vidhibiti vya vifaa"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Chagua programu ili uweke vidhibiti"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Data ya mtandao wa simu"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Imeunganishwa"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Data ya mtandao wa simu haitaunganishwa kiotomatiki"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Hakuna muunganisho"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Hakuna mitandao mingine inayopatikana"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 9397b6c65397..b291004b35c0 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்ஷன்"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"பயனர் அமைப்புகள்"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"முடிந்தது"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"மூடுக"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"இணைக்கப்பட்டது"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"மொபைல் டேட்டாவை ஆஃப் செய்யவா?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> மூலம் டேட்டா அல்லது இணையத்தை உங்களால் பயன்படுத்த முடியாது. வைஃபை வழியாக மட்டுமே இணையத்தைப் பயன்படுத்த முடியும்."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"உங்கள் மொபைல் நிறுவனம்"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"அனுமதிக் கோரிக்கையை ஆப்ஸ் மறைப்பதால், அமைப்புகளால் உங்கள் பதிலைச் சரிபார்க்க முடியாது."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை, <xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க அனுமதிக்கவா?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருந்து தகவலைப் படிக்கும்"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"சாளரத்தைப் பெரிதாக்கும் கருவிக்கான அமைப்புகள்"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"அணுகல்தன்மை அம்சத்தை திறக்க தட்டவும். அமைப்பில் பட்டனை பிரத்தியேகமாக்கலாம்/மாற்றலாம்.\n\n"<annotation id="link">"அமைப்பில் காண்க"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"பட்டனைத் தற்காலிகமாக மறைக்க ஓரத்திற்கு நகர்த்தும்"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"மேலே இடதுபுறத்திற்கு நகர்த்து"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"மேலே வலதுபுறத்திற்கு நகர்த்து"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"கீழே இடதுபுறத்திற்கு நகர்த்து"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"கீழே வலதுபுறத்திற்கு நகர்த்து"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ஓரத்திற்கு நகர்த்தி மறை"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ஓரத்திற்கு நகர்த்தி, காட்டு"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"நிலைமாற்று"</string> <string name="quick_controls_title" msgid="6839108006171302273">"சாதனக் கட்டுப்பாடுகள்"</string> <string name="controls_providers_title" msgid="6879775889857085056">"கட்டுப்பாடுகளைச் சேர்க்க வேண்டிய ஆப்ஸைத் தேர்ந்தெடுங்கள்"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"மொபைல் டேட்டா"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"இணைக்கப்பட்டது"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"மொபைல் டேட்டாவுடன் தானாக இணைக்காது"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"இணைப்பு இல்லை"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"வேறு நெட்வொர்க்குகள் எதுவும் கிடைக்கவில்லை"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index f202faeb28b6..4c34f45aee7d 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"యూజర్ సెట్టింగ్లు"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"పూర్తయింది"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"మూసివేయి"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"కనెక్ట్ చేయబడినది"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"మొబైల్ డేటాను ఆఫ్ చేయాలా?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"\"<xliff:g id="CARRIER">%s</xliff:g>\" ద్వారా మీకు డేటా లేదా ఇంటర్నెట్కు యాక్సెస్ ఉండదు. Wi-Fi ద్వారా మాత్రమే ఇంటర్నెట్ అందుబాటులో ఉంటుంది."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"మీ క్యారియర్"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"అనుమతి రిక్వెస్ట్కు ఒక యాప్ అడ్డు తగులుతున్నందున సెట్టింగ్లు మీ ప్రతిస్పందనను ధృవీకరించలేకపోయాయి."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించండి?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదువుతుంది"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"మాగ్నిఫయర్ విండో సెట్టింగ్లు"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"యాక్సెసిబిలిటీ ఫీచర్లను తెరవడానికి ట్యాప్ చేయండి. సెట్టింగ్లలో ఈ బటన్ను అనుకూలంగా మార్చండి లేదా రీప్లేస్ చేయండి.\n\n"<annotation id="link">"వీక్షణ సెట్టింగ్లు"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"తాత్కాలికంగా దానిని దాచడానికి బటన్ను చివరకు తరలించండి"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ఎగువ ఎడమ వైపునకు తరలించు"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ఎగువ కుడి వైపునకు తరలించు"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"దిగువ ఎడమ వైపునకు తరలించు"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"దిగువ కుడి వైపునకు తరలించు"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"అంచుకు తరలించి దాచండి"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"అంచుని తరలించి చూపించు"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"టోగుల్ చేయి"</string> <string name="quick_controls_title" msgid="6839108006171302273">"డివైజ్ కంట్రోల్స్"</string> <string name="controls_providers_title" msgid="6879775889857085056">"కంట్రోల్స్ను యాడ్ చేయడానికి యాప్ను ఎంచుకోండి"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"మొబైల్ డేటా"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"కనెక్ట్ చేయబడింది"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"మొబైల్ డేటా ఆటోమెటిక్గా కనెక్ట్ అవ్వదు"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"కనెక్షన్ లేదు"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ఇతర నెట్వర్క్లేవీ అందుబాటులో లేవు"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index f21643724e1b..5de908c1db73 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"การตั้งค่าผู้ใช้"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"จัดการผู้ใช้"</string> <string name="quick_settings_done" msgid="2163641301648855793">"เสร็จสิ้น"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ปิด"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"เชื่อมต่อ"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"ปิดอินเทอร์เน็ตมือถือไหม"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"คุณจะใช้เน็ตมือถือหรืออินเทอร์เน็ตผ่าน \"<xliff:g id="CARRIER">%s</xliff:g>\" ไม่ได้ แต่จะใช้ผ่าน Wi-Fi ได้เท่านั้น"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ผู้ให้บริการของคุณ"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"เนื่องจากแอปหนึ่งได้บดบังคำขอสิทธิ์ ระบบจึงไม่สามารถยืนยันคำตอบของคุณสำหรับการตั้งค่าได้"</string> <string name="slice_permission_title" msgid="3262615140094151017">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"การตั้งค่าหน้าต่างแว่นขยาย"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"แตะเพื่อเปิดฟีเจอร์การช่วยเหลือพิเศษ ปรับแต่งหรือแทนที่ปุ่มนี้ในการตั้งค่า\n\n"<annotation id="link">"ดูการตั้งค่า"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ย้ายปุ่มไปที่ขอบเพื่อซ่อนชั่วคราว"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ย้ายไปด้านซ้ายบน"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ย้ายไปด้านขวาบน"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ย้ายไปด้านซ้ายล่าง"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ย้ายไปด้านขาวล่าง"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ย้ายไปที่ขอบและซ่อน"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ย้ายออกจากขอบและแสดง"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"สลับ"</string> <string name="quick_controls_title" msgid="6839108006171302273">"ระบบควบคุมอุปกรณ์"</string> <string name="controls_providers_title" msgid="6879775889857085056">"เลือกแอปเพื่อเพิ่มตัวควบคุม"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"อินเทอร์เน็ตมือถือ"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"เชื่อมต่อแล้ว"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"อินเทอร์เน็ตมือถือจะไม่เชื่อมต่ออัตโนมัติ"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"ไม่มีการเชื่อมต่อ"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ไม่มีเครือข่ายอื่นๆ ที่พร้อมใช้งาน"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index f1acf43e81ae..f47b90669856 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -248,7 +248,7 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mga setting ng user"</string> + <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Pamahalaan ang mga user"</string> <string name="quick_settings_done" msgid="2163641301648855793">"Tapos na"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Isara"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Nakakonekta"</string> @@ -727,6 +727,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"I-off ang mobile data?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Hindi ka magkaka-access sa data o internet sa pamamagitan ng <xliff:g id="CARRIER">%s</xliff:g>. Available lang ang internet sa pamamagitan ng Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ang iyong carrier"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Hindi ma-verify ng Mga Setting ang iyong tugon dahil may app na tumatakip sa isang kahilingan sa pagpapahintulot."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +793,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Mga setting ng window ng magnifier"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"I-tap, buksan mga feature ng accessibility. I-customize o palitan button sa Mga Setting.\n\n"<annotation id="link">"Tingnan ang mga setting"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ilipat ang button sa gilid para pansamantala itong itago"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Ilipat sa kaliwa sa itaas"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Ilipat sa kanan sa itaas"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Ilipat sa kaliwa sa ibaba"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Ilipat sa kanan sa ibaba"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ilipat sa sulok at itago"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alisin sa sulok at ipakita"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"i-toggle"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Mga kontrol ng device"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Pumili ng app para magdagdag ng mga kontrol"</string> @@ -933,6 +947,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Nakakonekta"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Hindi awtomatikong kokonekta ang mobile data"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Walang koneksyon"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Walang available na iba pang network"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 7b52a418a2a9..17dfa48ec036 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kullanıcı ayarları"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Bitti"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Kapat"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Bağlı"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil veri kapatılsın mı?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> üzerinden veri veya internet erişiminiz olmayacak. İnternet yalnızca kablosuz bağlantı üzerinden kullanılabilecek."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatörünüz"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Bir uygulama bir izin isteğinin anlaşılmasını engellediğinden, Ayarlar, yanıtınızı doğrulayamıyor."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Büyüteç penceresi ayarları"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erişilebilirlik özelliklerini açmak için dokunun. Bu düğmeyi Ayarlar\'dan özelleştirin veya değiştirin.\n\n"<annotation id="link">"Ayarları göster"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düğmeyi geçici olarak gizlemek için kenara taşıyın"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sol üste taşı"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sağ üste taşı"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sol alta taşı"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sağ alta taşı"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Kenara taşıyıp gizle"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Kenarın dışına taşıyıp göster"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"değiştir"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Cihaz denetimleri"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Denetim eklemek için uygulama seçin"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil veri"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Bağlı"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil veri otomatik olarak bağlanmıyor"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yok"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Kullanılabilir başka ağ yok"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 6bd9e30ef560..ff1b59467e5e 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налаштування користувача"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрити"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Під’єднано"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Вимкнути мобільний Інтернет?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Ви не матимете доступу до даних чи Інтернету через оператора <xliff:g id="CARRIER">%s</xliff:g>. Інтернет буде доступний лише через Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Не вдається підтвердити вашу відповідь у налаштуваннях, оскільки інший додаток заступає запит на дозвіл."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Має доступ до інформації з додатка <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Налаштування розміру лупи"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Кнопка спеціальних можливостей. Змініть або замініть її в Налаштуваннях.\n\n"<annotation id="link">"Переглянути налаштування"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Щоб тимчасово сховати кнопку, перемістіть її на край екрана"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перемістити ліворуч угору"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перемістити праворуч угору"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перемістити ліворуч униз"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перемістити праворуч униз"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перемістити до краю, приховати"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Перемістити від краю, показати"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"перемкнути"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Керування пристроями"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Виберіть, для якого додатка налаштувати елементи керування"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобільний трафік"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Підключено"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобільний Інтернет не підключатиметься автоматично"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Немає з\'єднання"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Інші мережі недоступні"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index ec382b250c2f..10e0e680023d 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"صارف کی ترتیبات"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"ہو گیا"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"بند کریں"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"مربوط"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"موبائل ڈیٹا آف کریں؟"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"آپ کو <xliff:g id="CARRIER">%s</xliff:g> کے ذریعے ڈیٹا یا انٹرنیٹ تک رسائی حاصل نہیں ہوگی۔ انٹرنیٹ صرف Wi-Fi کے ذریعے دستیاب ہوگا۔"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"آپ کا کریئر"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"چونکہ ایک ایپ اجازت کی درخواست کو مبہم کر رہی ہے، لہذا ترتیبات آپ کے جواب کی توثیق نہیں کر سکتی ہیں۔"</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"میگنیفائر ونڈو کی ترتیبات"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ایکسیسبیلٹی خصوصیات کھولنے کے لیے تھپتھپائیں۔ ترتیبات میں اس بٹن کو حسب ضرورت بنائیں یا تبدیل کریں۔\n\n"<annotation id="link">"ترتیبات ملاحظہ کریں"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"عارضی طور پر بٹن کو چھپانے کے لئے اسے کنارے پر لے جائیں"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"اوپر بائیں جانب لے جائیں"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"اوپر دائیں جانب لے جائيں"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نیچے بائیں جانب لے جائیں"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نیچے دائیں جانب لے جائیں"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"EDGE پر لے جائیں اور چھپائیں"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"EDGE اور شو سے باہر منتقل کریں"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ٹوگل کریں"</string> <string name="quick_controls_title" msgid="6839108006171302273">"آلہ کے کنٹرولز"</string> <string name="controls_providers_title" msgid="6879775889857085056">"کنٹرولز شامل کرنے کے لیے ایپ منتخب کریں"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"موبائل ڈیٹا"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g> / <xliff:g id="STATE">%1$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"منسلک ہے"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"موبائل ڈیٹا خودکار طور پر منسلک نہیں ہوگا"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"کوئی کنکشن نہیں"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"کوئی دوسرا نیٹ ورک دستیاب نہیں ہے"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index a666432371c9..184044d6aa29 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Yorqinlik"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ranglarni akslantirish"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ranglarni tuzatish"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Foydalanuvchi sozlamalari"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Tayyor"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Yopish"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Ulangan"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil internet uzilsinmi?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> orqali internetdan foydalana olmaysiz. Internet faqat Wi-Fi orqali ishlaydi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"aloqa operatoringiz"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Ilova ruxsatnoma so‘roviga xalaqit qilayotgani tufayli, “Sozlamalar” ilovasi javobingizni tekshira olmaydi."</string> <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Lupa oynasi sozlamalari"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Maxsus imkoniyatlarni ochish uchun bosing Sozlamalardan moslay yoki almashtira olasiz.\n\n"<annotation id="link">"Sozlamalar"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Vaqtinchalik berkitish uchun tugmani qirra tomon suring"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuqori chapga surish"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Yuqori oʻngga surish"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Quyi chapga surish"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Quyi oʻngga surish"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Chetiga olib borish va yashirish"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Chetidan qaytarish va koʻrsatish"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"oʻzgartirish"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Qurilmalarni boshqarish"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Boshqaruv elementlarini kiritish uchun ilovani tanlang"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil internet"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Ulandi"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil internetga avtomatik ulanmaydi"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Internetga ulanmagansiz"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Boshqa tarmoqlar mavjud emas"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 21200ca7d5bd..8ea3408c039a 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cài đặt người dùng"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Xong"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Đóng"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Đã kết nối"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Tắt dữ liệu di động?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Bạn sẽ không có quyền sử dụng dữ liệu hoặc truy cập Internet thông qua chế độ <xliff:g id="CARRIER">%s</xliff:g>. Bạn chỉ có thể truy cập Internet thông qua Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"nhà mạng của bạn"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Vì ứng dụng đang che khuất yêu cầu cấp quyền nên Cài đặt không thể xác minh câu trả lời của bạn."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Chế độ cài đặt cửa sổ phóng to"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Nhấn để mở bộ tính năng hỗ trợ tiếp cận. Tuỳ chỉnh/thay thế nút này trong phần Cài đặt.\n\n"<annotation id="link">"Xem chế độ cài đặt"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Di chuyển nút sang cạnh để ẩn nút tạm thời"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Chuyển lên trên cùng bên trái"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Chuyển lên trên cùng bên phải"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Chuyển tới dưới cùng bên trái"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Chuyển tới dưới cùng bên phải"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Chuyển đến cạnh và ẩn"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Chuyển ra xa cạnh và hiển thị"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"bật/tắt"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Điều khiển thiết bị"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Chọn ứng dụng để thêm các tùy chọn điều khiển"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dữ liệu di động"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Đã kết nối"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Dữ liệu di động sẽ không tự động kết nối"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Không có kết nối mạng"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Không có mạng nào khác"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 83508a16e7e4..1cead42790b9 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"用户设置"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"关闭"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"已连接"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"要关闭移动数据网络吗?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"您将无法通过<xliff:g id="CARRIER">%s</xliff:g>使用移动数据或互联网,只能通过 WLAN 连接到互联网。"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的运营商"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"由于某个应用遮挡了权限请求界面,因此“设置”应用无法验证您的回应。"</string> <string name="slice_permission_title" msgid="3262615140094151017">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大镜窗口设置"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"点按即可打开无障碍功能。您可在“设置”中自定义或更换此按钮。\n\n"<annotation id="link">"查看设置"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"将按钮移到边缘,即可暂时将其隐藏"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移至左上角"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移至右上角"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移至左下角"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移至右下角"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移至边缘并隐藏"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"移至边缘以外并显示"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"开启/关闭"</string> <string name="quick_controls_title" msgid="6839108006171302273">"设备控制器"</string> <string name="controls_providers_title" msgid="6879775889857085056">"选择要添加控制器的应用"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"移动数据网络"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"已连接"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"系统将不会自动连接到移动数据网络"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"无网络连接"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"没有其他可用网络"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 28420b38f4ed..cb90614293e4 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"關閉"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"已連線"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉流動數據嗎?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"您無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用流動數據或互聯網。如要使用互聯網,您必須連接 Wi-Fi。"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的流動網絡供應商"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式已阻擋權限要求畫面,因此「設定」應用程式無法驗證您的回應。"</string> <string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大鏡視窗設定"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"㩒一下就可以開無障礙功能。喺「設定」度自訂或者取代呢個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣即可暫時隱藏"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移去左上方"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移去右上方"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移到左下方"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移去右下方"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移到邊緣並隱藏"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"從邊緣移出並顯示"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切換"</string> <string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string> <string name="controls_providers_title" msgid="6879775889857085056">"選擇要新增控制項的應用程式"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"流動數據"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"不會自動連線至流動數據"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有連線"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網絡"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index 8a1a9e2cb4bf..ccad340d68b0 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"關閉"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"已連線"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉行動數據嗎?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"你將無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用行動數據或網際網路。你只能透過 Wi-Fi 使用網際網路。"</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"你的電信業者"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式覆蓋了權限要求畫面,因此「設定」應用程式無法驗證你的回應。"</string> <string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大鏡視窗設定"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"輕觸即可開啟無障礙功能。你可以前往「設定」自訂或更換這個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣處即可暫時隱藏"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移到左上方"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移到右上方"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移到左下方"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移到右下方"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移到邊緣並隱藏"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"從邊緣移出並顯示"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切換"</string> <string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string> <string name="controls_providers_title" msgid="6879775889857085056">"選擇應用程式以新增控制項"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"行動數據"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"系統將不會自動使用行動數據連線"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有網路連線"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網路"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 9b376f0d4d67..b3464949a301 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -248,7 +248,8 @@ <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string> <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string> <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string> - <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Amasethingi womsebenzisi"</string> + <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) --> + <skip /> <string name="quick_settings_done" msgid="2163641301648855793">"Kwenziwe"</string> <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Vala"</string> <string name="quick_settings_connected" msgid="3873605509184830379">"Ixhunyiwe"</string> @@ -727,6 +728,14 @@ <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vala idatha yeselula?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Ngeke ube nokufinyelela kudatha noma ku-inthanethi nge-<xliff:g id="CARRIER">%s</xliff:g>. I-inthanethi izotholakala kuphela nge-Wi-Fi."</string> <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"inkampani yakho yenethiwekhi"</string> + <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) --> + <skip /> + <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) --> + <skip /> + <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) --> + <skip /> <string name="touch_filtered_warning" msgid="8119511393338714836">"Ngoba uhlelo lokusebenza lusitha isicelo semvume, Izilungiselelo azikwazi ukuqinisekisa impendulo yakho."</string> <string name="slice_permission_title" msgid="3262615140094151017">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string> <string name="slice_permission_text_1" msgid="6675965177075443714">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string> @@ -785,12 +794,18 @@ <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Amasethingi ewindi lesikhulisi"</string> <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Thepha ukuze uvule izakhi zokufinyelela. Enza ngendlela oyifisayo noma shintsha le nkinobho Kumasethingi.\n\n"<annotation id="link">"Buka amasethingi"</annotation></string> <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Hambisa inkinobho onqenqemeni ukuze uyifihle okwesikhashana"</string> + <!-- no translation found for accessibility_floating_button_undo (511112888715708241) --> + <skip /> + <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) --> + <skip /> <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Hamba phezulu kwesokunxele"</string> <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Hamba phezulu ngakwesokudla"</string> <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Hamba phansi ngakwesokunxele"</string> <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Hamba phansi ngakwesokudla"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Hamba onqenqemeni ufihle"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Phuma onqenqemeni ubonise"</string> + <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) --> + <skip /> <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"guqula"</string> <string name="quick_controls_title" msgid="6839108006171302273">"Izilawuli zezinsiza"</string> <string name="controls_providers_title" msgid="6879775889857085056">"Khetha uhlelo lokusebenza ukwengeza izilawuli"</string> @@ -933,6 +948,10 @@ <string name="mobile_data_settings_title" msgid="3955246641380064901">"Idatha yeselula"</string> <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string> <string name="mobile_data_connection_active" msgid="944490013299018227">"Ixhunyiwe"</string> + <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) --> + <skip /> + <!-- no translation found for mobile_data_poor_connection (819617772268371434) --> + <skip /> <string name="mobile_data_off_summary" msgid="3663995422004150567">"Idatha yeselula ngeke ikwazi ukuxhuma ngokuzenzekelayo"</string> <string name="mobile_data_no_connection" msgid="1713872434869947377">"Alukho uxhumano"</string> <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Awekho amanye amanethiwekhi atholakalayo"</string> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index e30d4415a0c4..8d4431520c75 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -35,4 +35,6 @@ <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at fade_out_complete_frame --> <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen> + + <integer name="qs_carrier_max_em">7</integer> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml index 9115d42fc42d..148e5ec1606f 100644 --- a/packages/SystemUI/res/xml/media_session_collapsed.xml +++ b/packages/SystemUI/res/xml/media_session_collapsed.xml @@ -34,6 +34,16 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> + <!-- Touch ripple must have the same constraint as the album art. --> + <Constraint + android:id="@+id/touch_ripple_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_collapsed" + app:layout_constraintStart_toStartOf="@+id/album_art" + app:layout_constraintEnd_toEndOf="@+id/album_art" + app:layout_constraintTop_toTopOf="@+id/album_art" + app:layout_constraintBottom_toBottomOf="@+id/album_art" /> + <Constraint android:id="@+id/header_title" android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml index 522dc686daa3..ac484d7dde8e 100644 --- a/packages/SystemUI/res/xml/media_session_expanded.xml +++ b/packages/SystemUI/res/xml/media_session_expanded.xml @@ -27,6 +27,16 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" /> + <!-- Touch ripple must have the same constraint as the album art. --> + <Constraint + android:id="@+id/touch_ripple_view" + android:layout_width="match_parent" + android:layout_height="@dimen/qs_media_session_height_expanded" + app:layout_constraintStart_toStartOf="@+id/album_art" + app:layout_constraintEnd_toEndOf="@+id/album_art" + app:layout_constraintTop_toTopOf="@+id/album_art" + app:layout_constraintBottom_toBottomOf="@+id/album_art" /> + <Constraint android:id="@+id/header_title" android:layout_width="wrap_content" diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt index da1d233949cf..3961438ff591 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -237,6 +237,9 @@ class DefaultClockController( ) { var isActive: Boolean = fraction < 0.5f fun update(newFraction: Float): Pair<Boolean, Boolean> { + if (newFraction == fraction) { + return Pair(isActive, false) + } val wasActive = isActive val hasJumped = (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f) diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index 4613e8b1060f..bfbe88c475ac 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -20,6 +20,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; import android.view.MotionEvent; +import android.view.SurfaceControl; import com.android.systemui.shared.recents.ISystemUiProxy; oneway interface IOverviewProxy { @@ -44,12 +45,6 @@ oneway interface IOverviewProxy { void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8; /** - * Sent when there was an action on one of the onboarding tips view. - * TODO: Move this implementation to SystemUI completely - */ - void onTip(int actionType, int viewType) = 10; - - /** * Sent when device assistant changes its default assistant whether it is available or not. */ void onAssistantAvailable(boolean available) = 13; @@ -60,13 +55,6 @@ oneway interface IOverviewProxy { void onAssistantVisibilityChanged(float visibility) = 14; /** - * Sent when back is triggered. - * TODO: Move this implementation to SystemUI completely - */ - void onBackAction(boolean completed, int downX, int downY, boolean isButton, - boolean gestureSwipeLeft) = 15; - - /** * Sent when some system ui state changes. */ void onSystemUiStateChanged(int stateFlags) = 16; @@ -110,4 +98,14 @@ oneway interface IOverviewProxy { * Sent when screen started turning off. */ void onScreenTurningOff() = 24; + + /** + * Sent when split keyboard shortcut is triggered to enter stage split. + */ + void enterStageSplitFromRunningApp(boolean leftOrTop) = 25; + + /** + * Sent when the surface for navigation bar is created or changed + */ + void onNavigationBarSurface(in SurfaceControl surface) = 26; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 2b2b05ce2fbf..1c532fe7a529 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -24,7 +24,6 @@ import android.os.UserHandle; import android.view.MotionEvent; import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.RemoteTransitionCompat; /** * Temporary callbacks into SystemUI. @@ -106,9 +105,6 @@ interface ISystemUiProxy { /** Sets home rotation enabled. */ void setHomeRotationEnabled(boolean enabled) = 45; - /** Notifies that a swipe-up gesture has started */ - oneway void notifySwipeUpGestureStarted() = 46; - /** Notifies when taskbar status updated */ oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt index cd4b9994ccca..0ee813b84402 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt @@ -24,15 +24,13 @@ import com.android.systemui.shared.navigationbar.RegionSamplingHelper.SamplingCa import java.io.PrintWriter import java.util.concurrent.Executor -/** - * Class for instance of RegionSamplingHelper - */ -open class RegionSamplingInstance( - sampledView: View?, - mainExecutor: Executor?, - bgExecutor: Executor?, - regionSamplingEnabled: Boolean, - updateFun: UpdateColorCallback +/** Class for instance of RegionSamplingHelper */ +open class RegionSampler( + sampledView: View?, + mainExecutor: Executor?, + bgExecutor: Executor?, + regionSamplingEnabled: Boolean, + updateFun: UpdateColorCallback ) { private var regionDarkness = RegionDarkness.DEFAULT private var samplingBounds = Rect() @@ -40,23 +38,13 @@ open class RegionSamplingInstance( @VisibleForTesting var regionSampler: RegionSamplingHelper? = null private var lightForegroundColor = Color.WHITE private var darkForegroundColor = Color.BLACK - /** - * Interface for method to be passed into RegionSamplingHelper - */ - @FunctionalInterface - interface UpdateColorCallback { - /** - * Method to update the foreground colors after clock darkness changed. - */ - fun updateColors() - } @VisibleForTesting open fun createRegionSamplingHelper( - sampledView: View, - callback: SamplingCallback, - mainExecutor: Executor?, - bgExecutor: Executor? + sampledView: View, + callback: SamplingCallback, + mainExecutor: Executor?, + bgExecutor: Executor? ): RegionSamplingHelper { return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor) } @@ -77,7 +65,7 @@ open class RegionSamplingInstance( * * @return the determined foreground color */ - fun currentForegroundColor(): Int{ + fun currentForegroundColor(): Int { return if (regionDarkness.isDark) { lightForegroundColor } else { @@ -97,41 +85,37 @@ open class RegionSamplingInstance( return regionDarkness } - /** - * Start region sampler - */ + /** Start region sampler */ fun startRegionSampler() { regionSampler?.start(samplingBounds) } - /** - * Stop region sampler - */ + /** Stop region sampler */ fun stopRegionSampler() { regionSampler?.stop() } - /** - * Dump region sampler - */ + /** Dump region sampler */ fun dump(pw: PrintWriter) { regionSampler?.dump(pw) } init { if (regionSamplingEnabled && sampledView != null) { - regionSampler = createRegionSamplingHelper(sampledView, + regionSampler = + createRegionSamplingHelper( + sampledView, object : SamplingCallback { override fun onRegionDarknessChanged(isRegionDark: Boolean) { regionDarkness = convertToClockDarkness(isRegionDark) - updateFun.updateColors() + updateFun() } /** - * The method getLocationOnScreen is used to obtain the view coordinates - * relative to its left and top edges on the device screen. - * Directly accessing the X and Y coordinates of the view returns the - * location relative to its parent view instead. - */ + * The method getLocationOnScreen is used to obtain the view coordinates + * relative to its left and top edges on the device screen. Directly + * accessing the X and Y coordinates of the view returns the location + * relative to its parent view instead. + */ override fun getSampledRegion(sampledView: View): Rect { val screenLocation = tmpScreenLocation sampledView.getLocationOnScreen(screenLocation) @@ -147,8 +131,13 @@ open class RegionSamplingInstance( override fun isSamplingEnabled(): Boolean { return regionSamplingEnabled } - }, mainExecutor, bgExecutor) + }, + mainExecutor, + bgExecutor + ) } regionSampler?.setWindowVisible(true) } } + +typealias UpdateColorCallback = () -> Unit diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java deleted file mode 100644 index 37e706a9a4c9..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.shared.system; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_OLD_NONE; -import static android.view.WindowManager.TRANSIT_OPEN; -import static android.view.WindowManager.TRANSIT_TO_BACK; -import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.view.WindowManager.TransitionOldType; -import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; - -import android.annotation.SuppressLint; -import android.app.IApplicationThread; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Log; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.IRemoteTransition; -import android.window.IRemoteTransitionFinishedCallback; -import android.window.RemoteTransition; -import android.window.TransitionInfo; - -import com.android.wm.shell.util.CounterRotator; - -/** - * @see RemoteAnimationAdapter - */ -public class RemoteAnimationAdapterCompat { - - private final RemoteAnimationAdapter mWrapped; - private final RemoteTransitionCompat mRemoteTransition; - - public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration, - long statusBarTransitionDelay, IApplicationThread appThread) { - mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration, - statusBarTransitionDelay); - mRemoteTransition = buildRemoteTransition(runner, appThread); - } - - public RemoteAnimationAdapter getWrapped() { - return mWrapped; - } - - /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */ - public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner, - IApplicationThread appThread) { - return new RemoteTransitionCompat( - new RemoteTransition(wrapRemoteTransition(runner), appThread)); - } - - public RemoteTransitionCompat getRemoteTransition() { - return mRemoteTransition; - } - - /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */ - public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner( - final RemoteAnimationRunnerCompat remoteAnimationAdapter) { - return new IRemoteAnimationRunner.Stub() { - @Override - public void onAnimationStart(@TransitionOldType int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - final IRemoteAnimationFinishedCallback finishedCallback) { - final Runnable animationFinishedCallback = new Runnable() { - @Override - public void run() { - try { - finishedCallback.onAnimationFinished(); - } catch (RemoteException e) { - Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" - + " finished callback", e); - } - } - }; - remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers, - nonApps, animationFinishedCallback); - } - - @Override - public void onAnimationCancelled(boolean isKeyguardOccluded) { - remoteAnimationAdapter.onAnimationCancelled(); - } - }; - } - - private static IRemoteTransition.Stub wrapRemoteTransition( - final RemoteAnimationRunnerCompat remoteAnimationAdapter) { - return new IRemoteTransition.Stub() { - final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>(); - - @Override - public void startAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) { - final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); - final RemoteAnimationTarget[] apps = - RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); - final RemoteAnimationTarget[] wallpapers = - RemoteAnimationTargetCompat.wrapNonApps( - info, true /* wallpapers */, t, leashMap); - final RemoteAnimationTarget[] nonApps = - RemoteAnimationTargetCompat.wrapNonApps( - info, false /* wallpapers */, t, leashMap); - - // TODO(b/177438007): Move this set-up logic into launcher's animation impl. - boolean isReturnToHome = false; - TransitionInfo.Change launcherTask = null; - TransitionInfo.Change wallpaper = null; - int launcherLayer = 0; - int rotateDelta = 0; - float displayW = 0; - float displayH = 0; - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - // skip changes that we didn't wrap - if (!leashMap.containsKey(change.getLeash())) continue; - if (change.getTaskInfo() != null - && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { - isReturnToHome = change.getMode() == TRANSIT_OPEN - || change.getMode() == TRANSIT_TO_FRONT; - launcherTask = change; - launcherLayer = info.getChanges().size() - i; - } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { - wallpaper = change; - } - if (change.getParent() == null && change.getEndRotation() >= 0 - && change.getEndRotation() != change.getStartRotation()) { - rotateDelta = change.getEndRotation() - change.getStartRotation(); - displayW = change.getEndAbsBounds().width(); - displayH = change.getEndAbsBounds().height(); - } - } - - // Prepare for rotation if there is one - final CounterRotator counterLauncher = new CounterRotator(); - final CounterRotator counterWallpaper = new CounterRotator(); - if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) { - counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(), - rotateDelta, displayW, displayH); - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), launcherLayer); - } - } - - if (isReturnToHome) { - if (counterLauncher.getSurface() != null) { - t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3); - } - // Need to "boost" the closing things since that's what launcher expects. - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final SurfaceControl leash = leashMap.get(change.getLeash()); - // skip changes that we didn't wrap - if (leash == null) continue; - final int mode = info.getChanges().get(i).getMode(); - // Only deal with independent layers - if (!TransitionInfo.isIndependent(change, info)) continue; - if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { - t.setLayer(leash, info.getChanges().size() * 3 - i); - counterLauncher.addChild(t, leash); - } - } - // Make wallpaper visible immediately since launcher apparently won't do this. - for (int i = wallpapers.length - 1; i >= 0; --i) { - t.show(wallpapers[i].leash); - t.setAlpha(wallpapers[i].leash, 1.f); - } - } else { - if (launcherTask != null) { - counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash())); - } - if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) { - counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(), - rotateDelta, displayW, displayH); - if (counterWallpaper.getSurface() != null) { - t.setLayer(counterWallpaper.getSurface(), -1); - counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash())); - } - } - } - t.apply(); - - final Runnable animationFinishedCallback = new Runnable() { - @Override - @SuppressLint("NewApi") - public void run() { - final SurfaceControl.Transaction finishTransaction = - new SurfaceControl.Transaction(); - counterLauncher.cleanUp(finishTransaction); - counterWallpaper.cleanUp(finishTransaction); - // Release surface references now. This is apparently to free GPU memory - // while doing quick operations (eg. during CTS). - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - info.getChanges().get(i).getLeash().release(); - } - // Don't release here since launcher might still be using them. Instead - // let launcher release them (eg. via RemoteAnimationTargets) - leashMap.clear(); - try { - finishCallback.onTransitionFinished(null /* wct */, finishTransaction); - } catch (RemoteException e) { - Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" - + " finished callback", e); - } - } - }; - synchronized (mFinishRunnables) { - mFinishRunnables.put(token, animationFinishedCallback); - } - // TODO(bc-unlcok): Pass correct transit type. - remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE, - apps, wallpapers, nonApps, () -> { - synchronized (mFinishRunnables) { - if (mFinishRunnables.remove(token) == null) return; - } - animationFinishedCallback.run(); - }); - } - - @Override - public void mergeAnimation(IBinder token, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishCallback) { - // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt - // to legacy cancel. - final Runnable finishRunnable; - synchronized (mFinishRunnables) { - finishRunnable = mFinishRunnables.remove(mergeTarget); - } - if (finishRunnable == null) return; - remoteAnimationAdapter.onAnimationCancelled(); - finishRunnable.run(); - } - }; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java deleted file mode 100644 index ab55037159ef..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.shared.system; - -import android.view.RemoteAnimationDefinition; - -/** - * @see RemoteAnimationDefinition - */ -public class RemoteAnimationDefinitionCompat { - - private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition(); - - public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) { - mWrapped.addRemoteAnimation(transition, adapter.getWrapped()); - } - - public void addRemoteAnimation(int transition, int activityTypeFilter, - RemoteAnimationAdapterCompat adapter) { - mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped()); - } - - public RemoteAnimationDefinition getWrapped() { - return mWrapped; - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java index 5809c8124946..93c807352521 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java @@ -16,12 +16,197 @@ package com.android.systemui.shared.system; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_NONE; +import static android.view.WindowManager.TRANSIT_OPEN; +import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.view.IRemoteAnimationFinishedCallback; +import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationTarget; +import android.view.SurfaceControl; import android.view.WindowManager; +import android.view.WindowManager.TransitionOldType; +import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; +import android.window.TransitionInfo; + +import com.android.wm.shell.util.CounterRotator; + +public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub { -public interface RemoteAnimationRunnerCompat { - void onAnimationStart(@WindowManager.TransitionOldType int transit, + public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, Runnable finishedCallback); - void onAnimationCancelled(); + + @Override + public final void onAnimationStart(@TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + final IRemoteAnimationFinishedCallback finishedCallback) { + + onAnimationStart(transit, apps, wallpapers, + nonApps, () -> { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + }); + } + + public IRemoteTransition toRemoteTransition() { + return new IRemoteTransition.Stub() { + final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>(); + + @Override + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) { + final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>(); + final RemoteAnimationTarget[] apps = + RemoteAnimationTargetCompat.wrapApps(info, t, leashMap); + final RemoteAnimationTarget[] wallpapers = + RemoteAnimationTargetCompat.wrapNonApps( + info, true /* wallpapers */, t, leashMap); + final RemoteAnimationTarget[] nonApps = + RemoteAnimationTargetCompat.wrapNonApps( + info, false /* wallpapers */, t, leashMap); + + // TODO(b/177438007): Move this set-up logic into launcher's animation impl. + boolean isReturnToHome = false; + TransitionInfo.Change launcherTask = null; + TransitionInfo.Change wallpaper = null; + int launcherLayer = 0; + int rotateDelta = 0; + float displayW = 0; + float displayH = 0; + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + // skip changes that we didn't wrap + if (!leashMap.containsKey(change.getLeash())) continue; + if (change.getTaskInfo() != null + && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) { + isReturnToHome = change.getMode() == TRANSIT_OPEN + || change.getMode() == TRANSIT_TO_FRONT; + launcherTask = change; + launcherLayer = info.getChanges().size() - i; + } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) { + wallpaper = change; + } + if (change.getParent() == null && change.getEndRotation() >= 0 + && change.getEndRotation() != change.getStartRotation()) { + rotateDelta = change.getEndRotation() - change.getStartRotation(); + displayW = change.getEndAbsBounds().width(); + displayH = change.getEndAbsBounds().height(); + } + } + + // Prepare for rotation if there is one + final CounterRotator counterLauncher = new CounterRotator(); + final CounterRotator counterWallpaper = new CounterRotator(); + if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) { + counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(), + rotateDelta, displayW, displayH); + if (counterLauncher.getSurface() != null) { + t.setLayer(counterLauncher.getSurface(), launcherLayer); + } + } + + if (isReturnToHome) { + if (counterLauncher.getSurface() != null) { + t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3); + } + // Need to "boost" the closing things since that's what launcher expects. + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final SurfaceControl leash = leashMap.get(change.getLeash()); + // skip changes that we didn't wrap + if (leash == null) continue; + final int mode = info.getChanges().get(i).getMode(); + // Only deal with independent layers + if (!TransitionInfo.isIndependent(change, info)) continue; + if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { + t.setLayer(leash, info.getChanges().size() * 3 - i); + counterLauncher.addChild(t, leash); + } + } + // Make wallpaper visible immediately since launcher apparently won't do this. + for (int i = wallpapers.length - 1; i >= 0; --i) { + t.show(wallpapers[i].leash); + t.setAlpha(wallpapers[i].leash, 1.f); + } + } else { + if (launcherTask != null) { + counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash())); + } + if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) { + counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(), + rotateDelta, displayW, displayH); + if (counterWallpaper.getSurface() != null) { + t.setLayer(counterWallpaper.getSurface(), -1); + counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash())); + } + } + } + t.apply(); + + final Runnable animationFinishedCallback = () -> { + final SurfaceControl.Transaction finishTransaction = + new SurfaceControl.Transaction(); + counterLauncher.cleanUp(finishTransaction); + counterWallpaper.cleanUp(finishTransaction); + // Release surface references now. This is apparently to free GPU memory + // while doing quick operations (eg. during CTS). + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + info.getChanges().get(i).getLeash().release(); + } + // Don't release here since launcher might still be using them. Instead + // let launcher release them (eg. via RemoteAnimationTargets) + leashMap.clear(); + try { + finishCallback.onTransitionFinished(null /* wct */, finishTransaction); + } catch (RemoteException e) { + Log.e("ActivityOptionsCompat", "Failed to call app controlled animation" + + " finished callback", e); + } + }; + synchronized (mFinishRunnables) { + mFinishRunnables.put(token, animationFinishedCallback); + } + // TODO(bc-unlcok): Pass correct transit type. + onAnimationStart(TRANSIT_OLD_NONE, + apps, wallpapers, nonApps, () -> { + synchronized (mFinishRunnables) { + if (mFinishRunnables.remove(token) == null) return; + } + animationFinishedCallback.run(); + }); + } + + @Override + public void mergeAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt + // to legacy cancel. + final Runnable finishRunnable; + synchronized (mFinishRunnables) { + finishRunnable = mFinishRunnables.remove(mergeTarget); + } + if (finishRunnable == null) return; + onAnimationCancelled(false /* isKeyguardOccluded */); + finishRunnable.run(); + } + }; + } }
\ No newline at end of file diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index d6655a74219c..d4d3d2579b10 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -18,29 +18,22 @@ package com.android.systemui.shared.system; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; -import static android.window.TransitionFilter.CONTAINER_ORDER_TOP; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IApplicationThread; -import android.content.ComponentName; import android.graphics.Rect; import android.os.IBinder; -import android.os.Parcelable; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; @@ -53,72 +46,23 @@ import android.window.IRemoteTransitionFinishedCallback; import android.window.PictureInPictureSurfaceTransaction; import android.window.RemoteTransition; import android.window.TaskSnapshot; -import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.DataClass; import com.android.systemui.shared.recents.model.ThumbnailData; import java.util.ArrayList; -import java.util.concurrent.Executor; /** - * Wrapper to expose RemoteTransition (shell transitions) to Launcher. - * - * @see IRemoteTransition - * @see TransitionFilter + * Helper class to build {@link RemoteTransition} objects */ -@DataClass -public class RemoteTransitionCompat implements Parcelable { +public class RemoteTransitionCompat { private static final String TAG = "RemoteTransitionCompat"; - @NonNull final RemoteTransition mTransition; - @Nullable TransitionFilter mFilter = null; - - RemoteTransitionCompat(RemoteTransition transition) { - mTransition = transition; - } - - public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner, - @NonNull Executor executor, @Nullable IApplicationThread appThread) { - IRemoteTransition remote = new IRemoteTransition.Stub() { - @Override - public void startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishedCallback) { - final Runnable finishAdapter = () -> { - try { - finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call transition finished callback", e); - } - }; - executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter)); - } - - @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - IRemoteTransitionFinishedCallback finishedCallback) { - final Runnable finishAdapter = () -> { - try { - finishedCallback.onTransitionFinished(null /* wct */, null /* sct */); - } catch (RemoteException e) { - Log.e(TAG, "Failed to call transition finished callback", e); - } - }; - executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget, - finishAdapter)); - } - }; - mTransition = new RemoteTransition(remote, appThread); - } - /** Constructor specifically for recents animation */ - public RemoteTransitionCompat(RecentsAnimationListener recents, + public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents, RecentsAnimationControllerCompat controller, IApplicationThread appThread) { IRemoteTransition remote = new IRemoteTransition.Stub() { final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap(); @@ -193,25 +137,7 @@ public class RemoteTransitionCompat implements Parcelable { mRecentsSession.commitTasksAppearedIfNeeded(recents); } }; - mTransition = new RemoteTransition(remote, appThread); - } - - /** Adds a filter check that restricts this remote transition to home open transitions. */ - public void addHomeOpenCheck(ComponentName homeActivity) { - if (mFilter == null) { - mFilter = new TransitionFilter(); - } - // No need to handle the transition that also dismisses keyguard. - mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; - mFilter.mRequirements = - new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(), - new TransitionFilter.Requirement()}; - mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME; - mFilter.mRequirements[0].mTopActivity = homeActivity; - mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; - mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP; - mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD; - mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; + return new RemoteTransition(remote, appThread); } /** @@ -505,161 +431,4 @@ public class RemoteTransitionCompat implements Parcelable { @Override public void animateNavigationBarToApp(long duration) { } } - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - @DataClass.Generated.Member - /* package-private */ RemoteTransitionCompat( - @NonNull RemoteTransition transition, - @Nullable TransitionFilter filter) { - this.mTransition = transition; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTransition); - this.mFilter = filter; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public @NonNull RemoteTransition getTransition() { - return mTransition; - } - - @DataClass.Generated.Member - public @Nullable TransitionFilter getFilter() { - return mFilter; - } - - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - byte flg = 0; - if (mFilter != null) flg |= 0x2; - dest.writeByte(flg); - dest.writeTypedObject(mTransition, flags); - if (mFilter != null) dest.writeTypedObject(mFilter, flags); - } - - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - protected RemoteTransitionCompat(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - byte flg = in.readByte(); - RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR); - TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR); - - this.mTransition = transition; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTransition); - this.mFilter = filter; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR - = new Parcelable.Creator<RemoteTransitionCompat>() { - @Override - public RemoteTransitionCompat[] newArray(int size) { - return new RemoteTransitionCompat[size]; - } - - @Override - public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) { - return new RemoteTransitionCompat(in); - } - }; - - /** - * A builder for {@link RemoteTransitionCompat} - */ - @SuppressWarnings("WeakerAccess") - @DataClass.Generated.Member - public static class Builder { - - private @NonNull RemoteTransition mTransition; - private @Nullable TransitionFilter mFilter; - - private long mBuilderFieldsSet = 0L; - - public Builder( - @NonNull RemoteTransition transition) { - mTransition = transition; - com.android.internal.util.AnnotationValidations.validate( - NonNull.class, null, mTransition); - } - - @DataClass.Generated.Member - public @NonNull Builder setTransition(@NonNull RemoteTransition value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x1; - mTransition = value; - return this; - } - - @DataClass.Generated.Member - public @NonNull Builder setFilter(@NonNull TransitionFilter value) { - checkNotUsed(); - mBuilderFieldsSet |= 0x2; - mFilter = value; - return this; - } - - /** Builds the instance. This builder should not be touched after calling this! */ - public @NonNull RemoteTransitionCompat build() { - checkNotUsed(); - mBuilderFieldsSet |= 0x4; // Mark builder used - - if ((mBuilderFieldsSet & 0x2) == 0) { - mFilter = null; - } - RemoteTransitionCompat o = new RemoteTransitionCompat( - mTransition, - mFilter); - return o; - } - - private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x4) != 0) { - throw new IllegalStateException( - "This Builder should not be reused. Use a new Builder instance instead"); - } - } - } - - @DataClass.Generated( - time = 1629321609807L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java", - inputSignatures = "private static final java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate android.window.WindowContainerToken mPausingTask\nprivate android.window.WindowContainerToken mPipTask\nprivate android.window.TransitionInfo mInfo\nprivate android.view.SurfaceControl mOpeningLeash\nprivate android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate android.os.IBinder mTransition\n void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java deleted file mode 100644 index accc456c4209..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.system; - -import android.os.IBinder; -import android.view.SurfaceControl; -import android.window.TransitionInfo; - -/** Interface for something that runs a remote transition animation. */ -public interface RemoteTransitionRunner { - /** - * Starts a transition animation. Once complete, the implementation should call - * `finishCallback`. - */ - void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, - Runnable finishCallback); - - /** - * Attempts to merge a transition into the currently-running animation. If merge is not - * possible/supported, this should do nothing. Otherwise, the implementation should call - * `finishCallback` immediately to indicate that it merged the transition. - * - * @param transition The transition that wants to be merged into the running animation. - * @param mergeTarget The transition to merge into (that this runner is currently animating). - */ - default void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { } -} diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 40a96b060bc0..d48d7ffcd824 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -34,25 +34,27 @@ import com.android.systemui.flags.Flags.DOZING_MIGRATION_1 import com.android.systemui.flags.Flags.REGION_SAMPLING import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.dagger.KeyguardClockLog import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.log.LogBuffer -import com.android.systemui.shared.regionsampling.RegionSamplingInstance +import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.ConfigurationController -import java.io.PrintWriter -import java.util.Locale -import java.util.TimeZone -import java.util.concurrent.Executor -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +import java.io.PrintWriter +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.Executor +import javax.inject.Inject /** * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by @@ -69,14 +71,17 @@ open class ClockEventController @Inject constructor( private val context: Context, @Main private val mainExecutor: Executor, @Background private val bgExecutor: Executor, - @KeyguardClockLog private val logBuffer: LogBuffer, + @KeyguardClockLog private val logBuffer: LogBuffer?, private val featureFlags: FeatureFlags ) { var clock: ClockController? = null set(value) { field = value if (value != null) { - value.setLogBuffer(logBuffer) + if (logBuffer != null) { + value.setLogBuffer(logBuffer) + } + value.initialize(resources, dozeAmount, 0f) updateRegionSamplers(value) } @@ -139,21 +144,17 @@ open class ClockEventController @Inject constructor( bgExecutor: Executor?, regionSamplingEnabled: Boolean, updateColors: () -> Unit - ): RegionSamplingInstance { - return RegionSamplingInstance( + ): RegionSampler { + return RegionSampler( sampledView, mainExecutor, bgExecutor, regionSamplingEnabled, - object : RegionSamplingInstance.UpdateColorCallback { - override fun updateColors() { - updateColors() - } - }) + updateFun = { updateColors() } ) } - var smallRegionSampler: RegionSamplingInstance? = null - var largeRegionSampler: RegionSamplingInstance? = null + var smallRegionSampler: RegionSampler? = null + var largeRegionSampler: RegionSampler? = null private var smallClockIsDark = true private var largeClockIsDark = true @@ -161,6 +162,7 @@ open class ClockEventController @Inject constructor( private val configListener = object : ConfigurationController.ConfigurationListener { override fun onThemeChanged() { clock?.events?.onColorPaletteChanged(resources) + updateColors() } override fun onDensityOrFontScaleChanged() { @@ -186,8 +188,10 @@ open class ClockEventController @Inject constructor( private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { override fun onKeyguardVisibilityChanged(visible: Boolean) { isKeyguardVisible = visible - if (!isKeyguardVisible) { - clock?.animations?.doze(if (isDozing) 1f else 0f) + if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { + if (!isKeyguardVisible) { + clock?.animations?.doze(if (isDozing) 1f else 0f) + } } } @@ -224,6 +228,7 @@ open class ClockEventController @Inject constructor( listenForDozing(this) if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { listenForDozeAmountTransition(this) + listenForGoneToAodTransition(this) } else { listenForDozeAmount(this) } @@ -276,6 +281,22 @@ open class ClockEventController @Inject constructor( } } + /** + * When keyguard is displayed again after being gone, the clock must be reset to full + * dozing. + */ + @VisibleForTesting + internal fun listenForGoneToAodTransition(scope: CoroutineScope): Job { + return scope.launch { + keyguardTransitionInteractor.goneToAodTransition.filter { + it.transitionState == TransitionState.STARTED + }.collect { + dozeAmount = 1f + clock?.animations?.doze(dozeAmount) + } + } + } + @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt index 71470e8870de..a0206f1f1e70 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt @@ -35,6 +35,7 @@ data class KeyguardFingerprintListenModel( val keyguardOccluded: Boolean, val occludingAppRequestingFp: Boolean, val primaryUser: Boolean, + val shouldListenSfpsState: Boolean, val shouldListenForFingerprintAssistant: Boolean, val switchingUser: Boolean, val udfps: Boolean, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 93ee151f26c5..c756a17976bf 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -89,6 +89,7 @@ import com.android.settingslib.Utils; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter; @@ -136,6 +137,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { private GlobalSettings mGlobalSettings; private FalsingManager mFalsingManager; private UserSwitcherController mUserSwitcherController; + private FalsingA11yDelegate mFalsingA11yDelegate; private AlertDialog mAlertDialog; private boolean mSwipeUpToRetry; @@ -318,7 +320,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager, UserSwitcherController userSwitcherController, - UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) { + UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback, + FalsingA11yDelegate falsingA11yDelegate) { if (mCurrentMode == mode) return; Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to " + modeToString(mode)); @@ -337,6 +340,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } mGlobalSettings = globalSettings; mFalsingManager = falsingManager; + mFalsingA11yDelegate = falsingA11yDelegate; mUserSwitcherController = userSwitcherController; setupViewMode(); } @@ -361,7 +365,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { } mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager, - mUserSwitcherController); + mUserSwitcherController, mFalsingA11yDelegate); } @Mode int getMode() { @@ -723,7 +727,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, - @NonNull UserSwitcherController userSwitcherController) {}; + @NonNull UserSwitcherController userSwitcherController, + @NonNull FalsingA11yDelegate falsingA11yDelegate) {}; /** Reinitialize the location */ default void updateSecurityViewLocation() {}; @@ -828,7 +833,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, - @NonNull UserSwitcherController userSwitcherController) { + @NonNull UserSwitcherController userSwitcherController, + @NonNull FalsingA11yDelegate falsingA11yDelegate) { mView = v; mViewFlipper = viewFlipper; @@ -865,6 +871,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { this::setupUserSwitcher; private UserSwitcherCallback mUserSwitcherCallback; + private FalsingA11yDelegate mFalsingA11yDelegate; UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) { mUserSwitcherCallback = userSwitcherCallback; @@ -874,13 +881,15 @@ public class KeyguardSecurityContainer extends ConstraintLayout { public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, - @NonNull UserSwitcherController userSwitcherController) { + @NonNull UserSwitcherController userSwitcherController, + @NonNull FalsingA11yDelegate falsingA11yDelegate) { init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false); mView = v; mViewFlipper = viewFlipper; mFalsingManager = falsingManager; mUserSwitcherController = userSwitcherController; mResources = v.getContext().getResources(); + mFalsingA11yDelegate = falsingA11yDelegate; if (mUserSwitcherViewGroup == null) { LayoutInflater.from(v.getContext()).inflate( @@ -978,6 +987,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { mUserSwitcher.setText(currentUserName); KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor); + anchor.setAccessibilityDelegate(mFalsingA11yDelegate); BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) { @Override @@ -1048,7 +1058,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout { anchor.setOnClickListener((v) -> { if (mFalsingManager.isFalseTap(LOW_PENALTY)) return; - mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager); + mPopup = new KeyguardUserSwitcherPopupMenu(mView.getContext(), mFalsingManager); mPopup.setAnchorView(anchor); mPopup.setAdapter(adapter); mPopup.setOnItemClickListener((parent, view, pos, id) -> { @@ -1137,7 +1147,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings, @NonNull KeyguardSecurityViewFlipper viewFlipper, @NonNull FalsingManager falsingManager, - @NonNull UserSwitcherController userSwitcherController) { + @NonNull UserSwitcherController userSwitcherController, + @NonNull FalsingA11yDelegate falsingA11yDelegate) { init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true); mView = v; mViewFlipper = viewFlipper; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 0b395a8760cf..79a01b9c9717 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -59,6 +59,7 @@ import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Gefingerpoken; import com.android.systemui.R; import com.android.systemui.biometrics.SidefpsController; +import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -100,6 +101,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final FeatureFlags mFeatureFlags; private final SessionTracker mSessionTracker; private final Optional<SidefpsController> mSidefpsController; + private final FalsingA11yDelegate mFalsingA11yDelegate; private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED; @@ -288,7 +290,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FeatureFlags featureFlags, GlobalSettings globalSettings, SessionTracker sessionTracker, - Optional<SidefpsController> sidefpsController) { + Optional<SidefpsController> sidefpsController, + FalsingA11yDelegate falsingA11yDelegate) { super(view); mLockPatternUtils = lockPatternUtils; mUpdateMonitor = keyguardUpdateMonitor; @@ -309,6 +312,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mGlobalSettings = globalSettings; mSessionTracker = sessionTracker; mSidefpsController = sidefpsController; + mFalsingA11yDelegate = falsingA11yDelegate; } @Override @@ -349,10 +353,21 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (!mSidefpsController.isPresent()) { return; } - if (mBouncerVisible - && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) - && mUpdateMonitor.isFingerprintDetectionRunning() - && !mUpdateMonitor.userNeedsStrongAuth()) { + final boolean sfpsEnabled = getResources().getBoolean( + R.bool.config_show_sidefps_hint_on_bouncer); + final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning(); + final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth(); + + boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth; + + if (DEBUG) { + Log.d(TAG, "sideFpsToShow=" + toShow + ", " + + "mBouncerVisible=" + mBouncerVisible + ", " + + "configEnabled=" + sfpsEnabled + ", " + + "fpsDetectionRunning=" + fpsDetectionRunning + ", " + + "needsStrongAuth=" + needsStrongAuth); + } + if (toShow) { mSidefpsController.get().show(); } else { mSidefpsController.get().hide(); @@ -625,7 +640,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue), - null)); + null), mFalsingA11yDelegate); } public void reportFailedUnlockAttempt(int userId, int timeoutMs) { @@ -730,6 +745,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final UserSwitcherController mUserSwitcherController; private final SessionTracker mSessionTracker; private final Optional<SidefpsController> mSidefpsController; + private final FalsingA11yDelegate mFalsingA11yDelegate; @Inject Factory(KeyguardSecurityContainer view, @@ -749,7 +765,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FeatureFlags featureFlags, GlobalSettings globalSettings, SessionTracker sessionTracker, - Optional<SidefpsController> sidefpsController) { + Optional<SidefpsController> sidefpsController, + FalsingA11yDelegate falsingA11yDelegate) { mView = view; mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory; mLockPatternUtils = lockPatternUtils; @@ -767,6 +784,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mUserSwitcherController = userSwitcherController; mSessionTracker = sessionTracker; mSidefpsController = sidefpsController; + mFalsingA11yDelegate = falsingA11yDelegate; } public KeyguardSecurityContainerController create( @@ -777,7 +795,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardStateController, securityCallback, mSecurityViewFlipperController, mConfigurationController, mFalsingCollector, mFalsingManager, mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker, - mSidefpsController); + mSidefpsController, mFalsingA11yDelegate); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 39dc609c9bb7..9d2dcf0ddd07 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -150,6 +150,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; +import com.android.systemui.util.settings.SecureSettings; import com.google.android.collect.Lists; @@ -321,17 +322,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>> mCallbacks = Lists.newArrayList(); private ContentObserver mDeviceProvisionedObserver; + private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver; private final ContentObserver mTimeFormatChangeObserver; private boolean mSwitchingUser; private boolean mDeviceInteractive; + private boolean mSfpsRequireScreenOnToAuthPrefEnabled; private final SubscriptionManager mSubscriptionManager; private final TelephonyListenerManager mTelephonyListenerManager; private final TrustManager mTrustManager; private final UserManager mUserManager; private final DevicePolicyManager mDevicePolicyManager; private final BroadcastDispatcher mBroadcastDispatcher; + private final SecureSettings mSecureSettings; private final InteractionJankMonitor mInteractionJankMonitor; private final LatencyTracker mLatencyTracker; private final StatusBarStateController mStatusBarStateController; @@ -380,6 +384,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected Handler getHandler() { return mHandler; } + private final Handler mHandler; private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = @@ -704,6 +709,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Request to listen for face authentication when an app is occluding keyguard. + * * @param request if true and mKeyguardOccluded, request face auth listening, else default * to normal behavior. * See {@link KeyguardUpdateMonitor#shouldListenForFace()} @@ -716,6 +722,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * Request to listen for fingerprint when an app is occluding keyguard. + * * @param request if true and mKeyguardOccluded, request fingerprint listening, else default * to normal behavior. * See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)} @@ -1930,6 +1937,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Context context, @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, + SecureSettings secureSettings, DumpManager dumpManager, @Background Executor backgroundExecutor, @Main Executor mainExecutor, @@ -1972,6 +1980,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStatusBarState = mStatusBarStateController.getState(); mLockPatternUtils = lockPatternUtils; mAuthController = authController; + mSecureSettings = secureSettings; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = sensorPrivacyManager; mActiveUnlockConfig = activeUnlockConfiguration; @@ -2214,9 +2223,35 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Settings.System.TIME_12_24))); } }; + mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(Settings.System.TIME_12_24), false, mTimeFormatChangeObserver, UserHandle.USER_ALL); + + updateSfpsRequireScreenOnToAuthPref(); + mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + updateSfpsRequireScreenOnToAuthPref(); + } + }; + + mContext.getContentResolver().registerContentObserver( + mSecureSettings.getUriFor( + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED), + false, + mSfpsRequireScreenOnToAuthPrefObserver, + getCurrentUser()); + } + + protected void updateSfpsRequireScreenOnToAuthPref() { + final int defaultSfpsRequireScreenOnToAuthValue = + mContext.getResources().getBoolean( + com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0; + mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser( + Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, + defaultSfpsRequireScreenOnToAuthValue, + getCurrentUser()) != 0; } private void initializeSimState() { @@ -2261,6 +2296,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } /** + * @return true if there's at least one sfps enrollment for the current user. + */ + public boolean isSfpsEnrolled() { + return mAuthController.isSfpsEnrolled(getCurrentUser()); + } + + /** + * @return true if sfps HW is supported on this device. Can return true even if the user has + * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. + */ + public boolean isSfpsSupported() { + return mAuthController.getSfpsProps() != null + && !mAuthController.getSfpsProps().isEmpty(); + } + + /** * @return true if there's at least one face enrolled */ public boolean isFaceEnrolled() { @@ -2583,13 +2634,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted); final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user); + final boolean shouldListenUdfpsState = !isUdfps || (!userCanSkipBouncer - && !isEncryptedOrLockdownForUser - && userDoesNotHaveTrust); + && !isEncryptedOrLockdownForUser + && userDoesNotHaveTrust); + + boolean shouldListenSideFpsState = true; + if (isSfpsSupported() && isSfpsEnrolled()) { + shouldListenSideFpsState = + mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true; + } - final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState - && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut(); + boolean shouldListen = shouldListenKeyguardState && shouldListenUserState + && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut() + && shouldListenSideFpsState; maybeLogListenerModelData( new KeyguardFingerprintListenModel( @@ -2611,6 +2670,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mKeyguardOccluded, mOccludingAppRequestingFp, mIsPrimaryUser, + shouldListenSideFpsState, shouldListenForFingerprintAssistant, mSwitchingUser, isUdfps, @@ -3712,6 +3772,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver); } + if (mSfpsRequireScreenOnToAuthPrefObserver != null) { + mContext.getContentResolver().unregisterContentObserver( + mSfpsRequireScreenOnToAuthPrefObserver); + } + try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { @@ -3784,6 +3849,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing); pw.println(" mStatusBarState=" + StatusBarState.toString(mStatusBarState)); pw.println(" mUdfpsBouncerShowing=" + mUdfpsBouncerShowing); + } else if (isSfpsSupported()) { + pw.println(" sfpsEnrolled=" + isSfpsEnrolled()); + pw.println(" shouldListenForSfps=" + shouldListenForFingerprint(false)); + if (isSfpsEnrolled()) { + pw.println(" mSfpsRequireScreenOnToAuthPrefEnabled=" + + mSfpsRequireScreenOnToAuthPrefEnabled); + } } } if (mFaceManager != null && mFaceManager.isHardwareDetected()) { diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index 46f3d4e5f6aa..32ce537ea25a 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -21,6 +21,7 @@ import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.ERROR +import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.plugins.log.LogLevel.VERBOSE import com.android.systemui.plugins.log.LogLevel.WARNING import com.android.systemui.plugins.log.MessageInitializer @@ -50,6 +51,14 @@ class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuf buffer.log(TAG, DEBUG, messageInitializer, messagePrinter) } + fun v(msg: String, arg: Any) { + buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" }) + } + + fun i(msg: String, arg: Any) { + buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" }) + } + // TODO: remove after b/237743330 is fixed fun logStatusBarCalculatedAlpha(alpha: Float) { debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" }) diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index d9f44cdecf40..47ee71e8982f 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -43,6 +43,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.systemui.dagger.GlobalRootComponent; import com.android.systemui.dagger.SysUIComponent; import com.android.systemui.dump.DumpManager; +import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.NotificationChannels; import java.util.Comparator; @@ -137,7 +138,7 @@ public class SystemUIApplication extends Application implements if (mServicesStarted) { final int N = mServices.length; for (int i = 0; i < N; i++) { - mServices[i].onBootCompleted(); + notifyBootCompleted(mServices[i]); } } } @@ -256,7 +257,7 @@ public class SystemUIApplication extends Application implements for (i = 0; i < mServices.length; i++) { if (mBootCompleteCache.isBootComplete()) { - mServices[i].onBootCompleted(); + notifyBootCompleted(mServices[i]); } mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]); @@ -267,7 +268,13 @@ public class SystemUIApplication extends Application implements mServicesStarted = true; } - private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log, + private static void notifyBootCompleted(CoreStartable coreStartable) { + Trace.beginSection(coreStartable.getClass().getSimpleName() + ".onBootCompleted()"); + coreStartable.onBootCompleted(); + Trace.endSection(); + } + + private static void timeInitialization(String clsName, Runnable init, TimingsTraceLog log, String metricsPrefix) { long ti = System.currentTimeMillis(); log.traceBegin(metricsPrefix + " " + clsName); @@ -281,11 +288,13 @@ public class SystemUIApplication extends Application implements } } - private CoreStartable startAdditionalStartable(String clsName) { + private static CoreStartable startAdditionalStartable(String clsName) { CoreStartable startable; if (DEBUG) Log.d(TAG, "loading: " + clsName); try { + Trace.beginSection(clsName + ".newInstance()"); startable = (CoreStartable) Class.forName(clsName).newInstance(); + Trace.endSection(); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ex) { @@ -295,14 +304,19 @@ public class SystemUIApplication extends Application implements return startStartable(startable); } - private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) { + private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) { if (DEBUG) Log.d(TAG, "loading: " + clsName); - return startStartable(provider.get()); + Trace.beginSection("Provider<" + clsName + ">.get()"); + CoreStartable startable = provider.get(); + Trace.endSection(); + return startStartable(startable); } - private CoreStartable startStartable(CoreStartable startable) { + private static CoreStartable startStartable(CoreStartable startable) { if (DEBUG) Log.d(TAG, "running: " + startable); + Trace.beginSection(startable.getClass().getSimpleName() + ".start()"); startable.start(); + Trace.endSection(); return startable; } @@ -340,11 +354,18 @@ public class SystemUIApplication extends Application implements @Override public void onConfigurationChanged(Configuration newConfig) { if (mServicesStarted) { - mSysUIComponent.getConfigurationController().onConfigurationChanged(newConfig); + ConfigurationController configController = mSysUIComponent.getConfigurationController(); + Trace.beginSection( + configController.getClass().getSimpleName() + ".onConfigurationChanged()"); + configController.onConfigurationChanged(newConfig); + Trace.endSection(); int len = mServices.length; for (int i = 0; i < len; i++) { if (mServices[i] != null) { + Trace.beginSection( + mServices[i].getClass().getSimpleName() + ".onConfigurationChanged()"); mServices[i].onConfigurationChanged(newConfig); + Trace.endSection(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index 034e96a13029..4a9807febc7f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -136,11 +136,7 @@ class MenuViewAppearance { final Rect draggableBounds = getWindowAvailableBounds(); // Initializes start position for mapping the translation of the menu view. - final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); - final WindowInsets windowInsets = windowMetrics.getWindowInsets(); - final Insets displayCutoutInsets = windowInsets.getInsetsIgnoringVisibility( - WindowInsets.Type.displayCutout()); - draggableBounds.offset(-displayCutoutInsets.left, -displayCutoutInsets.top); + draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0); draggableBounds.top += margin; draggableBounds.right -= getMenuWidth(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java index b1a64eda46ff..c7be907efc64 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -74,7 +74,8 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu { params.receiveInsetsIgnoringZOrder = true; params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; params.windowAnimations = android.R.style.Animation_Translucent; - params.setFitInsetsTypes(WindowInsets.Type.navigationBars()); + params.setFitInsetsTypes( + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); return params; } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 313ff4157155..0cf3cfeaacd4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -52,7 +52,7 @@ import android.hardware.face.IFaceAuthenticatorsRegisteredCallback; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; @@ -147,7 +147,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull private final WindowManager mWindowManager; @NonNull private final DisplayManager mDisplayManager; @Nullable private UdfpsController mUdfpsController; - @Nullable private IUdfpsHbmListener mUdfpsHbmListener; + @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback; @Nullable private SidefpsController mSidefpsController; @Nullable private IBiometricContextListener mBiometricContextListener; @Nullable private UdfpsLogger mUdfpsLogger; @@ -160,6 +160,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; @NonNull private final SparseBooleanArray mFaceEnrolledForUser; + @NonNull private final SparseBooleanArray mSfpsEnrolledForUser; @NonNull private final SensorPrivacyManager mSensorPrivacyManager; private final WakefulnessLifecycle mWakefulnessLifecycle; private boolean mAllFingerprintAuthenticatorsRegistered; @@ -365,6 +366,15 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } } } + if (mSidefpsProps == null) { + Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null"); + } else { + for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) { + if (prop.sensorId == sensorId) { + mSfpsEnrolledForUser.put(userId, hasEnrollments); + } + } + } for (Callback cb : mCallbacks) { cb.onEnrollmentsChanged(modality); } @@ -722,6 +732,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, mWindowManager = windowManager; mInteractionJankMonitor = jankMonitor; mUdfpsEnrolledForUser = new SparseBooleanArray(); + mSfpsEnrolledForUser = new SparseBooleanArray(); mFaceEnrolledForUser = new SparseBooleanArray(); mVibratorHelper = vibrator; @@ -872,21 +883,22 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, } /** - * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}. + * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}. * - * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by - * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}. + * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback} + * and registers it with this class by calling + * {@link CommandQueue#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)}. */ @Override - public void setUdfpsHbmListener(IUdfpsHbmListener listener) { - mUdfpsHbmListener = listener; + public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { + mUdfpsRefreshRateRequestCallback = callback; } /** - * @return IUdfpsHbmListener that can be set by DisplayModeDirector. + * @return IUdfpsRefreshRateRequestCallback that can be set by DisplayModeDirector. */ - @Nullable public IUdfpsHbmListener getUdfpsHbmListener() { - return mUdfpsHbmListener; + @Nullable public IUdfpsRefreshRateRequestCallback getUdfpsRefreshRateCallback() { + return mUdfpsRefreshRateRequestCallback; } @Override @@ -964,6 +976,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, return mUdfpsProps; } + @Nullable + public List<FingerprintSensorPropertiesInternal> getSfpsProps() { + return mSidefpsProps; + } + private String getErrorString(@Modality int modality, int error, int vendorCode) { switch (modality) { case TYPE_FACE: @@ -1090,6 +1107,17 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, return mUdfpsEnrolledForUser.get(userId); } + /** + * Whether the passed userId has enrolled SFPS. + */ + public boolean isSfpsEnrolled(int userId) { + if (mSidefpsController == null) { + return false; + } + + return mSfpsEnrolledForUser.get(userId); + } + /** If BiometricPrompt is currently being shown to the user. */ public boolean isShowing() { return mCurrentDialog != null; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 48c60a0548ad..a091ed928a4d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Point; +import android.graphics.Rect; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.FingerprintManager; @@ -63,6 +64,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -133,6 +135,7 @@ public class UdfpsController implements DozeReceiver { @NonNull private final LatencyTracker mLatencyTracker; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; + @NonNull private final BouncerInteractor mBouncerInteractor; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @@ -219,7 +222,8 @@ public class UdfpsController implements DozeReceiver { mUnlockedScreenOffAnimationController, mUdfpsDisplayMode, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, - fromUdfpsView), mActivityLaunchAnimator))); + fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags, + mBouncerInteractor))); } @Override @@ -296,6 +300,30 @@ public class UdfpsController implements DozeReceiver { mOverlay.getOverlayView().setDebugMessage(message); }); } + + public Rect getSensorBounds() { + return mOverlayParams.getSensorBounds(); + } + + /** + * Passes a mocked MotionEvent to OnTouch. + * + * @param event MotionEvent to simulate in onTouch + */ + public void debugOnTouch(long requestId, MotionEvent event) { + UdfpsController.this.onTouch(requestId, event, false); + } + + /** + * Debug to run onUiReady + */ + public void debugOnUiReady(long requestId, int sensorId) { + if (UdfpsController.this.mAlternateTouchProvider != null) { + UdfpsController.this.mAlternateTouchProvider.onUiReady(); + } else { + UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId); + } + } } /** @@ -620,7 +648,8 @@ public class UdfpsController implements DozeReceiver { @NonNull LatencyTracker latencyTracker, @NonNull ActivityLaunchAnimator activityLaunchAnimator, @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider, - @BiometricsBackground Executor biometricsExecutor) { + @BiometricsBackground Executor biometricsExecutor, + @NonNull BouncerInteractor bouncerInteractor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -651,6 +680,7 @@ public class UdfpsController implements DozeReceiver { mActivityLaunchAnimator = activityLaunchAnimator; mAlternateTouchProvider = alternateTouchProvider.orElse(null); mBiometricExecutor = biometricsExecutor; + mBouncerInteractor = bouncerInteractor; mOrientationListener = new BiometricDisplayListener( context, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 7d0109686351..d70861ac5f19 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -48,6 +48,8 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -70,29 +72,31 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" */ @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( - private val context: Context, - fingerprintManager: FingerprintManager, - private val inflater: LayoutInflater, - private val windowManager: WindowManager, - private val accessibilityManager: AccessibilityManager, - private val statusBarStateController: StatusBarStateController, - private val shadeExpansionStateManager: ShadeExpansionStateManager, - private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - private val dialogManager: SystemUIDialogManager, - private val dumpManager: DumpManager, - private val transitionController: LockscreenShadeTransitionController, - private val configurationController: ConfigurationController, - private val systemClock: SystemClock, - private val keyguardStateController: KeyguardStateController, - private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, - val requestId: Long, - @ShowReason val requestReason: Int, - private val controllerCallback: IUdfpsOverlayControllerCallback, - private val onTouch: (View, MotionEvent, Boolean) -> Boolean, - private val activityLaunchAnimator: ActivityLaunchAnimator, - private val isDebuggable: Boolean = Build.IS_DEBUGGABLE + private val context: Context, + fingerprintManager: FingerprintManager, + private val inflater: LayoutInflater, + private val windowManager: WindowManager, + private val accessibilityManager: AccessibilityManager, + private val statusBarStateController: StatusBarStateController, + private val shadeExpansionStateManager: ShadeExpansionStateManager, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dialogManager: SystemUIDialogManager, + private val dumpManager: DumpManager, + private val transitionController: LockscreenShadeTransitionController, + private val configurationController: ConfigurationController, + private val systemClock: SystemClock, + private val keyguardStateController: KeyguardStateController, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider, + val requestId: Long, + @ShowReason val requestReason: Int, + private val controllerCallback: IUdfpsOverlayControllerCallback, + private val onTouch: (View, MotionEvent, Boolean) -> Boolean, + private val activityLaunchAnimator: ActivityLaunchAnimator, + private val featureFlags: FeatureFlags, + private val bouncerInteractor: BouncerInteractor, + private val isDebuggable: Boolean = Build.IS_DEBUGGABLE ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -246,7 +250,9 @@ class UdfpsControllerOverlay @JvmOverloads constructor( unlockedScreenOffAnimationController, dialogManager, controller, - activityLaunchAnimator + activityLaunchAnimator, + featureFlags, + bouncerInteractor ) } REASON_AUTH_BP -> { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt index b80b8a0ff219..670a8e6389af 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt @@ -46,7 +46,7 @@ constructor( logger.e(TAG, "enable | already requested") return } - if (authController.udfpsHbmListener == null) { + if (authController.udfpsRefreshRateCallback == null) { logger.e(TAG, "enable | mDisplayManagerCallback is null") return } @@ -60,7 +60,7 @@ constructor( try { // This method is a misnomer. It has nothing to do with HBM, its purpose is to set // the appropriate display refresh rate. - authController.udfpsHbmListener!!.onHbmEnabled(request.displayId) + authController.udfpsRefreshRateCallback!!.onRequestEnabled(request.displayId) logger.v(TAG, "enable | requested optimal refresh rate for UDFPS") } catch (e: RemoteException) { logger.e(TAG, "enable", e) @@ -84,7 +84,7 @@ constructor( try { // Allow DisplayManager to unset the UDFPS refresh rate. - authController.udfpsHbmListener!!.onHbmDisabled(request.displayId) + authController.udfpsRefreshRateCallback!!.onRequestDisabled(request.displayId) logger.v(TAG, "disable | removed the UDFPS refresh rate request") } catch (e: RemoteException) { logger.e(TAG, "disable", e) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java deleted file mode 100644 index 4d7f89d7b727..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ /dev/null @@ -1,548 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.biometrics; - -import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; - -import android.animation.ValueAnimator; -import android.annotation.NonNull; -import android.content.res.Configuration; -import android.util.MathUtils; -import android.view.MotionEvent; - -import com.android.keyguard.BouncerPanelExpansionCalculator; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.R; -import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.animation.Interpolators; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.ShadeExpansionChangeEvent; -import com.android.systemui.shade.ShadeExpansionListener; -import com.android.systemui.shade.ShadeExpansionStateManager; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.stack.StackStateAnimator; -import com.android.systemui.statusbar.phone.KeyguardBouncer; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.time.SystemClock; - -import java.io.PrintWriter; - -/** - * Class that coordinates non-HBM animations during keyguard authentication. - */ -public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> { - public static final String TAG = "UdfpsKeyguardViewCtrl"; - @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; - @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController; - @NonNull private final ConfigurationController mConfigurationController; - @NonNull private final SystemClock mSystemClock; - @NonNull private final KeyguardStateController mKeyguardStateController; - @NonNull private final UdfpsController mUdfpsController; - @NonNull private final UnlockedScreenOffAnimationController - mUnlockedScreenOffAnimationController; - @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; - private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f); - - private boolean mShowingUdfpsBouncer; - private boolean mUdfpsRequested; - private float mQsExpansion; - private boolean mFaceDetectRunning; - private int mStatusBarState; - private float mTransitionToFullShadeProgress; - private float mLastDozeAmount; - private long mLastUdfpsBouncerShowTime = -1; - private float mPanelExpansionFraction; - private boolean mLaunchTransitionFadingAway; - private boolean mIsLaunchingActivity; - private float mActivityLaunchProgress; - - /** - * hidden amount of pin/pattern/password bouncer - * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to - * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f) - */ - private float mInputBouncerHiddenAmount; - private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible - - protected UdfpsKeyguardViewController( - @NonNull UdfpsKeyguardView view, - @NonNull StatusBarStateController statusBarStateController, - @NonNull ShadeExpansionStateManager shadeExpansionStateManager, - @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, - @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull DumpManager dumpManager, - @NonNull LockscreenShadeTransitionController transitionController, - @NonNull ConfigurationController configurationController, - @NonNull SystemClock systemClock, - @NonNull KeyguardStateController keyguardStateController, - @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, - @NonNull SystemUIDialogManager systemUIDialogManager, - @NonNull UdfpsController udfpsController, - @NonNull ActivityLaunchAnimator activityLaunchAnimator) { - super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager, - dumpManager); - mKeyguardViewManager = statusBarKeyguardViewManager; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mLockScreenShadeTransitionController = transitionController; - mConfigurationController = configurationController; - mSystemClock = systemClock; - mKeyguardStateController = keyguardStateController; - mUdfpsController = udfpsController; - mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; - mActivityLaunchAnimator = activityLaunchAnimator; - - mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN); - mUnlockedScreenOffDozeAnimator.addUpdateListener( - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mView.onDozeAmountChanged( - animation.getAnimatedFraction(), - (float) animation.getAnimatedValue(), - UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF); - } - }); - } - - @Override - @NonNull protected String getTag() { - return "UdfpsKeyguardViewController"; - } - - @Override - public void onInit() { - super.onInit(); - mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); - } - - @Override - protected void onViewAttached() { - super.onViewAttached(); - final float dozeAmount = getStatusBarStateController().getDozeAmount(); - mLastDozeAmount = dozeAmount; - mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount); - getStatusBarStateController().addCallback(mStateListener); - - mUdfpsRequested = false; - - mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway(); - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); - mStatusBarState = getStatusBarStateController().getState(); - mQsExpansion = mKeyguardViewManager.getQsExpansion(); - updateGenericBouncerVisibility(); - mConfigurationController.addCallback(mConfigurationListener); - getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener); - updateScaleFactor(); - mView.updatePadding(); - updateAlpha(); - updatePauseAuth(); - - mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor); - mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this); - mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener); - } - - @Override - protected void onViewDetached() { - super.onViewDetached(); - mFaceDetectRunning = false; - - mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback); - getStatusBarStateController().removeCallback(mStateListener); - mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor); - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); - mConfigurationController.removeCallback(mConfigurationListener); - getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener); - if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { - mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); - } - mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener); - } - - @Override - public void dump(PrintWriter pw, String[] args) { - super.dump(pw, args); - pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer); - pw.println("mFaceDetectRunning=" + mFaceDetectRunning); - pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState)); - pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress); - pw.println("mQsExpansion=" + mQsExpansion); - pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing); - pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount); - pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction); - pw.println("unpausedAlpha=" + mView.getUnpausedAlpha()); - pw.println("mUdfpsRequested=" + mUdfpsRequested); - pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway); - pw.println("mLastDozeAmount=" + mLastDozeAmount); - - mView.dump(pw); - } - - /** - * Overrides non-bouncer show logic in shouldPauseAuth to still show icon. - * @return whether the udfpsBouncer has been newly shown or hidden - */ - private boolean showUdfpsBouncer(boolean show) { - if (mShowingUdfpsBouncer == show) { - return false; - } - - boolean udfpsAffordanceWasNotShowing = shouldPauseAuth(); - mShowingUdfpsBouncer = show; - if (mShowingUdfpsBouncer) { - mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis(); - } - if (mShowingUdfpsBouncer) { - if (udfpsAffordanceWasNotShowing) { - mView.animateInUdfpsBouncer(null); - } - - if (mKeyguardStateController.isOccluded()) { - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true); - } - - mView.announceForAccessibility(mView.getContext().getString( - R.string.accessibility_fingerprint_bouncer)); - } else { - mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); - } - - updateGenericBouncerVisibility(); - updateAlpha(); - updatePauseAuth(); - return true; - } - - /** - * Returns true if the fingerprint manager is running but we want to temporarily pause - * authentication. On the keyguard, we may want to show udfps when the shade - * is expanded, so this can be overridden with the showBouncer method. - */ - public boolean shouldPauseAuth() { - if (mShowingUdfpsBouncer) { - return false; - } - - if (mUdfpsRequested && !getNotificationShadeVisible() - && (!mIsGenericBouncerShowing - || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE) - && mKeyguardStateController.isShowing()) { - return false; - } - - if (mLaunchTransitionFadingAway) { - return true; - } - - // Only pause auth if we're not on the keyguard AND we're not transitioning to doze - // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is - // delayed. However, we still animate in the UDFPS affordance with the - // mUnlockedScreenOffDozeAnimator. - if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) { - return true; - } - - if (mInputBouncerHiddenAmount < .5f) { - return true; - } - - if (mView.getUnpausedAlpha() < (255 * .1)) { - return true; - } - - return false; - } - - @Override - public boolean listenForTouchesOutsideView() { - return true; - } - - @Override - public void onTouchOutsideView() { - maybeShowInputBouncer(); - } - - /** - * If we were previously showing the udfps bouncer, hide it and instead show the regular - * (pin/pattern/password) bouncer. - * - * Does nothing if we weren't previously showing the UDFPS bouncer. - */ - private void maybeShowInputBouncer() { - if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { - mKeyguardViewManager.showBouncer(true); - } - } - - /** - * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside - * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password - * bouncer. - */ - private boolean hasUdfpsBouncerShownWithMinTime() { - return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200; - } - - /** - * Set the progress we're currently transitioning to the full shade. 0.0f means we're not - * transitioning yet, while 1.0f means we've fully dragged down. - * - * For example, start swiping down to expand the notification shade from the empty space in - * the middle of the lock screen. - */ - public void setTransitionToFullShadeProgress(float progress) { - mTransitionToFullShadeProgress = progress; - updateAlpha(); - } - - /** - * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's - * alpha is based on the doze amount. - */ - @Override - public void updateAlpha() { - // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested, - // then the keyguard is occluded by some application - so instead use the input bouncer - // hidden amount to determine the fade. - float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction; - - int alpha = mShowingUdfpsBouncer ? 255 - : (int) MathUtils.constrain( - MathUtils.map(.5f, .9f, 0f, 255f, expansion), - 0f, 255f); - - if (!mShowingUdfpsBouncer) { - // swipe from top of the lockscreen to expand full QS: - alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion)); - - // swipe from the middle (empty space) of lockscreen to expand the notification shade: - alpha *= (1.0f - mTransitionToFullShadeProgress); - - // Fade out the icon if we are animating an activity launch over the lockscreen and the - // activity didn't request the UDFPS. - if (mIsLaunchingActivity && !mUdfpsRequested) { - alpha *= (1.0f - mActivityLaunchProgress); - } - - // Fade out alpha when a dialog is shown - // Fade in alpha when a dialog is hidden - alpha *= mView.getDialogSuggestedAlpha(); - } - mView.setUnpausedAlpha(alpha); - } - - /** - * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the - * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not. - */ - private void updateGenericBouncerVisibility() { - mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer - final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth(); - if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) { - mInputBouncerHiddenAmount = 1f; - } else if (mIsGenericBouncerShowing) { - // input bouncer is fully showing - mInputBouncerHiddenAmount = 0f; - } - } - - /** - * Update the scale factor based on the device's resolution. - */ - private void updateScaleFactor() { - if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) { - mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor()); - } - } - - private final StatusBarStateController.StateListener mStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onDozeAmountChanged(float linear, float eased) { - if (mLastDozeAmount < linear) { - showUdfpsBouncer(false); - } - mUnlockedScreenOffDozeAnimator.cancel(); - final boolean animatingFromUnlockedScreenOff = - mUnlockedScreenOffAnimationController.isAnimationPlaying(); - if (animatingFromUnlockedScreenOff && linear != 0f) { - // we manually animate the fade in of the UDFPS icon since the unlocked - // screen off animation prevents the doze amounts to be incrementally eased in - mUnlockedScreenOffDozeAnimator.start(); - } else { - mView.onDozeAmountChanged(linear, eased, - UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN); - } - - mLastDozeAmount = linear; - updatePauseAuth(); - } - - @Override - public void onStateChanged(int statusBarState) { - mStatusBarState = statusBarState; - updateAlpha(); - updatePauseAuth(); - } - }; - - private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor = - new StatusBarKeyguardViewManager.AlternateAuthInterceptor() { - @Override - public boolean showAlternateAuthBouncer() { - return showUdfpsBouncer(true); - } - - @Override - public boolean hideAlternateAuthBouncer() { - return showUdfpsBouncer(false); - } - - @Override - public boolean isShowingAlternateAuthBouncer() { - return mShowingUdfpsBouncer; - } - - @Override - public void requestUdfps(boolean request, int color) { - mUdfpsRequested = request; - mView.requestUdfps(request, color); - updateAlpha(); - updatePauseAuth(); - } - - @Override - public boolean isAnimating() { - return false; - } - - /** - * Set the amount qs is expanded. Forxample, swipe down from the top of the - * lock screen to start the full QS expansion. - */ - @Override - public void setQsExpansion(float qsExpansion) { - mQsExpansion = qsExpansion; - updateAlpha(); - updatePauseAuth(); - } - - @Override - public boolean onTouch(MotionEvent event) { - if (mTransitionToFullShadeProgress != 0) { - return false; - } - return mUdfpsController.onTouch(event); - } - - @Override - public void setBouncerExpansionChanged(float expansion) { - mInputBouncerHiddenAmount = expansion; - updateAlpha(); - updatePauseAuth(); - } - - /** - * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer - * visibility changes. - */ - @Override - public void onBouncerVisibilityChanged() { - updateGenericBouncerVisibility(); - updateAlpha(); - updatePauseAuth(); - } - - @Override - public void dump(PrintWriter pw) { - pw.println(getTag()); - } - }; - - private final ConfigurationController.ConfigurationListener mConfigurationListener = - new ConfigurationController.ConfigurationListener() { - @Override - public void onUiModeChanged() { - mView.updateColor(); - } - - @Override - public void onThemeChanged() { - mView.updateColor(); - } - - @Override - public void onConfigChanged(Configuration newConfig) { - updateScaleFactor(); - mView.updatePadding(); - mView.updateColor(); - } - }; - - private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() { - @Override - public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) { - float fraction = event.getFraction(); - mPanelExpansionFraction = - mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator - .aboutToShowBouncerProgress(fraction) : fraction; - updateAlpha(); - updatePauseAuth(); - } - }; - - private final KeyguardStateController.Callback mKeyguardStateControllerCallback = - new KeyguardStateController.Callback() { - @Override - public void onLaunchTransitionFadingAwayChanged() { - mLaunchTransitionFadingAway = - mKeyguardStateController.isLaunchTransitionFadingAway(); - updatePauseAuth(); - } - }; - - private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener = - new ActivityLaunchAnimator.Listener() { - @Override - public void onLaunchAnimationStart() { - mIsLaunchingActivity = true; - mActivityLaunchProgress = 0f; - updateAlpha(); - } - - @Override - public void onLaunchAnimationEnd() { - mIsLaunchingActivity = false; - updateAlpha(); - } - - @Override - public void onLaunchAnimationProgress(float linearProgress) { - mActivityLaunchProgress = linearProgress; - updateAlpha(); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt new file mode 100644 index 000000000000..5bae2dc502d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt @@ -0,0 +1,550 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.animation.ValueAnimator +import android.content.res.Configuration +import android.util.MathUtils +import android.view.MotionEvent +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionListener +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.notification.stack.StackStateAnimator +import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.time.SystemClock +import java.io.PrintWriter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** Class that coordinates non-HBM animations during keyguard authentication. */ +open class UdfpsKeyguardViewController +constructor( + private val view: UdfpsKeyguardView, + statusBarStateController: StatusBarStateController, + shadeExpansionStateManager: ShadeExpansionStateManager, + private val keyguardViewManager: StatusBarKeyguardViewManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + dumpManager: DumpManager, + private val lockScreenShadeTransitionController: LockscreenShadeTransitionController, + private val configurationController: ConfigurationController, + private val systemClock: SystemClock, + private val keyguardStateController: KeyguardStateController, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + systemUIDialogManager: SystemUIDialogManager, + private val udfpsController: UdfpsController, + private val activityLaunchAnimator: ActivityLaunchAnimator, + featureFlags: FeatureFlags, + private val bouncerInteractor: BouncerInteractor +) : + UdfpsAnimationViewController<UdfpsKeyguardView>( + view, + statusBarStateController, + shadeExpansionStateManager, + systemUIDialogManager, + dumpManager + ) { + private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER) + private var showingUdfpsBouncer = false + private var udfpsRequested = false + private var qsExpansion = 0f + private var faceDetectRunning = false + private var statusBarState = 0 + private var transitionToFullShadeProgress = 0f + private var lastDozeAmount = 0f + private var lastUdfpsBouncerShowTime: Long = -1 + private var panelExpansionFraction = 0f + private var launchTransitionFadingAway = false + private var isLaunchingActivity = false + private var activityLaunchProgress = 0f + private val unlockedScreenOffDozeAnimator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong() + interpolator = Interpolators.ALPHA_IN + addUpdateListener { animation -> + view.onDozeAmountChanged( + animation.animatedFraction, + animation.animatedValue as Float, + UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF + ) + } + } + /** + * Hidden amount of input (pin/pattern/password) bouncer. This is used + * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only + * used for the non-modernBouncer. + */ + private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN + private var inputBouncerExpansion = 0f // only used for modernBouncer + + private val stateListener: StatusBarStateController.StateListener = + object : StatusBarStateController.StateListener { + override fun onDozeAmountChanged(linear: Float, eased: Float) { + if (lastDozeAmount < linear) { + showUdfpsBouncer(false) + } + unlockedScreenOffDozeAnimator.cancel() + val animatingFromUnlockedScreenOff = + unlockedScreenOffAnimationController.isAnimationPlaying() + if (animatingFromUnlockedScreenOff && linear != 0f) { + // we manually animate the fade in of the UDFPS icon since the unlocked + // screen off animation prevents the doze amounts to be incrementally eased in + unlockedScreenOffDozeAnimator.start() + } else { + view.onDozeAmountChanged( + linear, + eased, + UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN + ) + } + lastDozeAmount = linear + updatePauseAuth() + } + + override fun onStateChanged(statusBarState: Int) { + this@UdfpsKeyguardViewController.statusBarState = statusBarState + updateAlpha() + updatePauseAuth() + } + } + + private val bouncerExpansionCallback: BouncerExpansionCallback = + object : BouncerExpansionCallback { + override fun onExpansionChanged(expansion: Float) { + inputBouncerHiddenAmount = expansion + updateAlpha() + updatePauseAuth() + } + + override fun onVisibilityChanged(isVisible: Boolean) { + updateBouncerHiddenAmount() + updateAlpha() + updatePauseAuth() + } + } + + private val configurationListener: ConfigurationController.ConfigurationListener = + object : ConfigurationController.ConfigurationListener { + override fun onUiModeChanged() { + view.updateColor() + } + + override fun onThemeChanged() { + view.updateColor() + } + + override fun onConfigChanged(newConfig: Configuration) { + updateScaleFactor() + view.updatePadding() + view.updateColor() + } + } + + private val shadeExpansionListener = ShadeExpansionListener { (fraction) -> + panelExpansionFraction = + if (keyguardViewManager.isBouncerInTransit) { + aboutToShowBouncerProgress(fraction) + } else { + fraction + } + updateAlpha() + updatePauseAuth() + } + + private val keyguardStateControllerCallback: KeyguardStateController.Callback = + object : KeyguardStateController.Callback { + override fun onLaunchTransitionFadingAwayChanged() { + launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway + updatePauseAuth() + } + } + + private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener = + object : ActivityLaunchAnimator.Listener { + override fun onLaunchAnimationStart() { + isLaunchingActivity = true + activityLaunchProgress = 0f + updateAlpha() + } + + override fun onLaunchAnimationEnd() { + isLaunchingActivity = false + updateAlpha() + } + + override fun onLaunchAnimationProgress(linearProgress: Float) { + activityLaunchProgress = linearProgress + updateAlpha() + } + } + + private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback = + object : KeyguardViewManagerCallback { + override fun onQSExpansionChanged(qsExpansion: Float) { + this@UdfpsKeyguardViewController.qsExpansion = qsExpansion + updateAlpha() + updatePauseAuth() + } + + /** + * Forward touches to the UdfpsController. This allows the touch to start from outside + * the sensor area and then slide their finger into the sensor area. + */ + override fun onTouch(event: MotionEvent) { + // Don't forward touches if the shade has already started expanding. + if (transitionToFullShadeProgress != 0f) { + return + } + udfpsController.onTouch(event) + } + } + + private val alternateAuthInterceptor: AlternateAuthInterceptor = + object : AlternateAuthInterceptor { + override fun showAlternateAuthBouncer(): Boolean { + return showUdfpsBouncer(true) + } + + override fun hideAlternateAuthBouncer(): Boolean { + return showUdfpsBouncer(false) + } + + override fun isShowingAlternateAuthBouncer(): Boolean { + return showingUdfpsBouncer + } + + override fun requestUdfps(request: Boolean, color: Int) { + udfpsRequested = request + view.requestUdfps(request, color) + updateAlpha() + updatePauseAuth() + } + + override fun dump(pw: PrintWriter) { + pw.println(tag) + } + } + + override val tag: String + get() = TAG + + override fun onInit() { + super.onInit() + keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor) + } + + init { + if (isModernBouncerEnabled) { + view.repeatWhenAttached { + // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion + // can make the view not visible; and we still want to listen for events + // that may make the view visible again. + repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) } + } + } + } + + @VisibleForTesting + internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { + return scope.launch { + bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float -> + inputBouncerExpansion = bouncerExpansion + updateAlpha() + updatePauseAuth() + } + } + } + + public override fun onViewAttached() { + super.onViewAttached() + val dozeAmount = statusBarStateController.dozeAmount + lastDozeAmount = dozeAmount + stateListener.onDozeAmountChanged(dozeAmount, dozeAmount) + statusBarStateController.addCallback(stateListener) + udfpsRequested = false + launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway + keyguardStateController.addCallback(keyguardStateControllerCallback) + statusBarState = statusBarStateController.state + qsExpansion = keyguardViewManager.qsExpansion + keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback) + if (!isModernBouncerEnabled) { + val bouncer = keyguardViewManager.bouncer + bouncer?.expansion?.let { + bouncerExpansionCallback.onExpansionChanged(it) + bouncer.addBouncerExpansionCallback(bouncerExpansionCallback) + } + updateBouncerHiddenAmount() + } + configurationController.addCallback(configurationListener) + shadeExpansionStateManager.addExpansionListener(shadeExpansionListener) + updateScaleFactor() + view.updatePadding() + updateAlpha() + updatePauseAuth() + keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor) + lockScreenShadeTransitionController.udfpsKeyguardViewController = this + activityLaunchAnimator.addListener(activityLaunchAnimatorListener) + } + + override fun onViewDetached() { + super.onViewDetached() + faceDetectRunning = false + keyguardStateController.removeCallback(keyguardStateControllerCallback) + statusBarStateController.removeCallback(stateListener) + keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor) + keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) + configurationController.removeCallback(configurationListener) + shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener) + if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) { + lockScreenShadeTransitionController.udfpsKeyguardViewController = null + } + activityLaunchAnimator.removeListener(activityLaunchAnimatorListener) + keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback) + if (!isModernBouncerEnabled) { + keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback) + } + } + + override fun dump(pw: PrintWriter, args: Array<String>) { + super.dump(pw, args) + pw.println("isModernBouncerEnabled=$isModernBouncerEnabled") + pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer") + pw.println("faceDetectRunning=$faceDetectRunning") + pw.println("statusBarState=" + StatusBarState.toString(statusBarState)) + pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress") + pw.println("qsExpansion=$qsExpansion") + pw.println("panelExpansionFraction=$panelExpansionFraction") + pw.println("unpausedAlpha=" + view.unpausedAlpha) + pw.println("udfpsRequestedByApp=$udfpsRequested") + pw.println("launchTransitionFadingAway=$launchTransitionFadingAway") + pw.println("lastDozeAmount=$lastDozeAmount") + if (isModernBouncerEnabled) { + pw.println("inputBouncerExpansion=$inputBouncerExpansion") + } else { + pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount") + } + view.dump(pw) + } + + /** + * Overrides non-bouncer show logic in shouldPauseAuth to still show icon. + * @return whether the udfpsBouncer has been newly shown or hidden + */ + private fun showUdfpsBouncer(show: Boolean): Boolean { + if (showingUdfpsBouncer == show) { + return false + } + val udfpsAffordanceWasNotShowing = shouldPauseAuth() + showingUdfpsBouncer = show + if (showingUdfpsBouncer) { + lastUdfpsBouncerShowTime = systemClock.uptimeMillis() + } + if (showingUdfpsBouncer) { + if (udfpsAffordanceWasNotShowing) { + view.animateInUdfpsBouncer(null) + } + if (keyguardStateController.isOccluded) { + keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true) + } + view.announceForAccessibility( + view.context.getString(R.string.accessibility_fingerprint_bouncer) + ) + } else { + keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false) + } + updateBouncerHiddenAmount() + updateAlpha() + updatePauseAuth() + return true + } + + /** + * Returns true if the fingerprint manager is running but we want to temporarily pause + * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so + * this can be overridden with the showBouncer method. + */ + override fun shouldPauseAuth(): Boolean { + if (showingUdfpsBouncer) { + return false + } + if ( + udfpsRequested && + !notificationShadeVisible && + !isInputBouncerFullyVisible() && + keyguardStateController.isShowing + ) { + return false + } + if (launchTransitionFadingAway) { + return true + } + + // Only pause auth if we're not on the keyguard AND we're not transitioning to doze + // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is + // delayed. However, we still animate in the UDFPS affordance with the + // mUnlockedScreenOffDozeAnimator. + if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) { + return true + } + if (isBouncerExpansionGreaterThan(.5f)) { + return true + } + return view.unpausedAlpha < 255 * .1 + } + + fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean { + return if (isModernBouncerEnabled) { + inputBouncerExpansion >= bouncerExpansionThreshold + } else { + inputBouncerHiddenAmount < bouncerExpansionThreshold + } + } + + fun isInputBouncerFullyVisible(): Boolean { + return if (isModernBouncerEnabled) { + inputBouncerExpansion == 1f + } else { + keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth + } + } + + override fun listenForTouchesOutsideView(): Boolean { + return true + } + + override fun onTouchOutsideView() { + maybeShowInputBouncer() + } + + /** + * If we were previously showing the udfps bouncer, hide it and instead show the regular + * (pin/pattern/password) bouncer. + * + * Does nothing if we weren't previously showing the UDFPS bouncer. + */ + private fun maybeShowInputBouncer() { + if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) { + keyguardViewManager.showBouncer(true) + } + } + + /** + * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the + * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer. + */ + private fun hasUdfpsBouncerShownWithMinTime(): Boolean { + return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200 + } + + /** + * Set the progress we're currently transitioning to the full shade. 0.0f means we're not + * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down + * to expand the notification shade from the empty space in the middle of the lock screen. + */ + fun setTransitionToFullShadeProgress(progress: Float) { + transitionToFullShadeProgress = progress + updateAlpha() + } + + /** + * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is + * based on the doze amount. + */ + override fun updateAlpha() { + // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested, + // then the keyguard is occluded by some application - so instead use the input bouncer + // hidden amount to determine the fade. + val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction + var alpha: Int = + if (showingUdfpsBouncer) 255 + else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt() + if (!showingUdfpsBouncer) { + // swipe from top of the lockscreen to expand full QS: + alpha = + (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion))) + .toInt() + + // swipe from the middle (empty space) of lockscreen to expand the notification shade: + alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt() + + // Fade out the icon if we are animating an activity launch over the lockscreen and the + // activity didn't request the UDFPS. + if (isLaunchingActivity && !udfpsRequested) { + alpha = (alpha * (1.0f - activityLaunchProgress)).toInt() + } + + // Fade out alpha when a dialog is shown + // Fade in alpha when a dialog is hidden + alpha = (alpha * view.dialogSuggestedAlpha).toInt() + } + view.unpausedAlpha = alpha + } + + private fun getInputBouncerHiddenAmt(): Float { + return if (isModernBouncerEnabled) { + 1f - inputBouncerExpansion + } else { + inputBouncerHiddenAmount + } + } + + /** Update the scale factor based on the device's resolution. */ + private fun updateScaleFactor() { + udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } + } + + private fun updateBouncerHiddenAmount() { + if (isModernBouncerEnabled) { + return + } + val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth + if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) { + inputBouncerHiddenAmount = 1f + } else if (keyguardViewManager.isBouncerShowing) { + // input bouncer is fully showing + inputBouncerHiddenAmount = 0f + } + } + + companion object { + const val TAG = "UdfpsKeyguardViewController" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt index da50f1c94f29..f48cfd3dc3a4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics import android.content.Context +import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER @@ -27,6 +28,11 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.util.Log import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.ACTION_UP +import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry @@ -36,6 +42,8 @@ import javax.inject.Inject private const val TAG = "UdfpsShell" private const val REQUEST_ID = 2L private const val SENSOR_ID = 0 +private const val MINOR = 10F +private const val MAJOR = 10F /** * Used to show and hide the UDFPS overlay with statusbar commands. @@ -67,6 +75,12 @@ class UdfpsShell @Inject constructor( hideUdfpsOverlay() } else if (args.size == 2 && args[0] == "show") { showOverlay(getEnrollmentReason(args[1])) + } else if (args.size == 1 && args[0] == "onUiReady") { + onUiReady() + } else if (args.size == 1 && args[0] == "simFingerDown") { + simFingerDown() + } else if (args.size == 1 && args[0] == "simFingerUp") { + simFingerUp() } else { invalidCommand(pw) } @@ -80,6 +94,11 @@ class UdfpsShell @Inject constructor( "auth-keyguard, auth-other, auth-settings]") pw.println(" -> reason otherwise defaults to unknown") pw.println(" - hide") + pw.println(" - onUiReady") + pw.println(" - simFingerDown") + pw.println(" -> Simulates onFingerDown on sensor") + pw.println(" - simFingerUp") + pw.println(" -> Simulates onFingerUp on sensor") } private fun invalidCommand(pw: PrintWriter) { @@ -125,4 +144,54 @@ class UdfpsShell @Inject constructor( private fun hideOverlay() { udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID) } + + + @VisibleForTesting + fun onUiReady() { + udfpsOverlayController?.debugOnUiReady(REQUEST_ID, SENSOR_ID) + } + + @VisibleForTesting + fun simFingerDown() { + val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds + + val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(), + sensorBounds.exactCenterY(), MINOR, MAJOR) + udfpsOverlayController?.debugOnTouch(REQUEST_ID, downEvent) + + val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(), + sensorBounds.exactCenterY(), MINOR, MAJOR) + udfpsOverlayController?.debugOnTouch(REQUEST_ID, moveEvent) + + downEvent?.recycle() + moveEvent?.recycle() + } + + @VisibleForTesting + fun simFingerUp() { + val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds + + val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(), + sensorBounds.exactCenterY(), MINOR, MAJOR) + udfpsOverlayController?.debugOnTouch(REQUEST_ID, upEvent) + upEvent?.recycle() + } + + private fun obtainMotionEvent( + action: Int, + x: Float, + y: Float, + minor: Float, + major: Float + ): MotionEvent? { + val pp = MotionEvent.PointerProperties() + pp.id = 1 + val pc = MotionEvent.PointerCoords() + pc.x = x + pc.y = y + pc.touchMinor = minor + pc.touchMajor = major + return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc), + 0, 0, 1f, 1f, 0, 0, 0, 0) + } } diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt index 22dc94a2c3f9..5850c9537ef0 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt @@ -21,6 +21,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.os.Handler import android.os.Looper +import android.os.Trace import android.os.UserHandle import android.util.ArrayMap import android.util.ArraySet @@ -126,6 +127,7 @@ open class UserBroadcastDispatcher( action, userId, { + Trace.beginSection("registerReceiver act=$action user=$userId") context.registerReceiverAsUser( this, UserHandle.of(userId), @@ -134,11 +136,14 @@ open class UserBroadcastDispatcher( workerHandler, flags ) + Trace.endSection() logger.logContextReceiverRegistered(userId, flags, it) }, { try { + Trace.beginSection("unregisterReceiver act=$action user=$userId") context.unregisterReceiver(this) + Trace.endSection() logger.logContextReceiverUnregistered(userId, action) } catch (e: IllegalArgumentException) { Log.e(TAG, "Trying to unregister unregistered receiver for user $userId, " + diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt index dec3d6b679c2..616e49c0b709 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt @@ -149,7 +149,7 @@ class WiredChargingRippleController @Inject constructor( } fun startRipple() { - if (rippleView.rippleInProgress || rippleView.parent != null) { + if (rippleView.rippleInProgress() || rippleView.parent != null) { // Skip if ripple is still playing, or not playing but already added the parent // (which might happen just before the animation starts or right after // the animation ends.) diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index c0cc6b41aafd..145569919e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -33,9 +33,9 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.ripple.RippleAnimationConfig; import com.android.systemui.ripple.RippleShader.RippleShape; import com.android.systemui.ripple.RippleView; -import com.android.systemui.ripple.RippleViewKt; import java.text.NumberFormat; @@ -150,7 +150,7 @@ final class WirelessChargingLayout extends FrameLayout { mRippleView.setColor(color, 28); } else { mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); - mRippleView.setColor(color, RippleViewKt.RIPPLE_DEFAULT_ALPHA); + mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA); } OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index 500f28004429..2245d8462c31 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -337,7 +337,8 @@ public class BrightLineFalsingManager implements FalsingManager { || mTestHarness || mDataProvider.isJustUnlockedWithFace() || mDataProvider.isDocked() - || mAccessibilityManager.isTouchExplorationEnabled(); + || mAccessibilityManager.isTouchExplorationEnabled() + || mDataProvider.isA11yAction(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt new file mode 100644 index 000000000000..63d57cc3fc8d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.classifier + +import android.os.Bundle +import android.view.View +import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK +import javax.inject.Inject + +/** + * Class that injects an artificial tap into the falsing collector. + * + * This is used for views that can be interacted with by A11y services and have falsing checks, as + * the gestures made by the A11y framework do not propagate motion events down the view hierarchy. + */ +class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) : + View.AccessibilityDelegate() { + override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean { + if (action == ACTION_CLICK) { + falsingCollector.onA11yAction() + } + return super.performAccessibilityAction(host, action, args) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java index 858bac30880b..66701080ddfb 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java @@ -132,5 +132,8 @@ public interface FalsingCollector { /** */ void updateFalseConfidence(FalsingClassifier.Result result); + + /** Indicates an a11y action was made. */ + void onA11yAction(); } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java index 0b7d6ab5acf7..cc25368161eb 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java @@ -157,4 +157,8 @@ public class FalsingCollectorFake implements FalsingCollector { @Override public void updateFalseConfidence(FalsingClassifier.Result result) { } + + @Override + public void onA11yAction() { + } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index da3d293d543b..8bdef1304fa4 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -375,6 +375,15 @@ class FalsingCollectorImpl implements FalsingCollector { mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis()); } + @Override + public void onA11yAction() { + if (mPendingDownEvent != null) { + mPendingDownEvent.recycle(); + mPendingDownEvent = null; + } + mFalsingDataProvider.onA11yAction(); + } + private boolean shouldSessionBeActive() { return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod; } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 3991a35e958a..09ebeeac163f 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -59,6 +59,7 @@ public class FalsingDataProvider { private MotionEvent mFirstRecentMotionEvent; private MotionEvent mLastMotionEvent; private boolean mJustUnlockedWithFace; + private boolean mA11YAction; @Inject public FalsingDataProvider( @@ -124,6 +125,7 @@ public class FalsingDataProvider { mPriorMotionEvents = mRecentMotionEvents; mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS); } + mA11YAction = false; } /** Returns screen width in pixels. */ @@ -334,6 +336,17 @@ public class FalsingDataProvider { mGestureFinalizedListeners.remove(listener); } + /** Return whether last gesture was an A11y action. */ + public boolean isA11yAction() { + return mA11YAction; + } + + /** Set whether last gesture was an A11y action. */ + public void onA11yAction() { + completePriorGesture(); + this.mA11YAction = true; + } + void onSessionStarted() { mSessionListeners.forEach(SessionListener::onSessionStarted); } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 9f338d1c6e1d..c853671519c0 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -31,6 +31,7 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT; +import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR; import static java.util.Objects.requireNonNull; @@ -73,6 +74,7 @@ import com.android.systemui.R; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; import java.io.IOException; @@ -101,6 +103,8 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardOverlayWindow mWindow; private final TimeoutHandler mTimeoutHandler; private final TextClassifier mTextClassifier; + private final ClipboardOverlayUtils mClipboardUtils; + private final FeatureFlags mFeatureFlags; private final ClipboardOverlayView mView; @@ -119,11 +123,15 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private Animator mExitAnimator; private Animator mEnterAnimator; + private Runnable mOnUiUpdate; + private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks = new ClipboardOverlayView.ClipboardOverlayCallbacks() { @Override public void onInteraction() { - mTimeoutHandler.resetTimeout(); + if (mOnUiUpdate != null) { + mOnUiUpdate.run(); + } } @Override @@ -178,7 +186,10 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv ClipboardOverlayWindow clipboardOverlayWindow, BroadcastDispatcher broadcastDispatcher, BroadcastSender broadcastSender, - TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) { + TimeoutHandler timeoutHandler, + FeatureFlags featureFlags, + ClipboardOverlayUtils clipboardUtils, + UiEventLogger uiEventLogger) { mBroadcastDispatcher = broadcastDispatcher; mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class)); final Context displayContext = context.createDisplayContext(getDefaultDisplay()); @@ -199,6 +210,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv mTimeoutHandler = timeoutHandler; mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS); + mFeatureFlags = featureFlags; + mClipboardUtils = clipboardUtils; + mView.setCallbacks(mClipboardCallbacks); @@ -257,11 +271,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null && clipData.getDescription().getExtras() .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE); + boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR) + && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource); if (clipData == null || clipData.getItemCount() == 0) { mView.showDefaultTextPreview(); } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { ClipData.Item item = clipData.getItemAt(0); - if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, + if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) { if (item.getTextLinks() != null) { AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource)); @@ -287,7 +303,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv maybeShowRemoteCopy(clipData); animateIn(); mView.announceForAccessibility(accessibilityAnnouncement); - mTimeoutHandler.resetTimeout(); + if (isRemote) { + mTimeoutHandler.cancelTimeout(); + mOnUiUpdate = null; + } else { + mOnUiUpdate = mTimeoutHandler::resetTimeout; + mOnUiUpdate.run(); + } } private void maybeShowRemoteCopy(ClipData clipData) { @@ -427,7 +449,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mTimeoutHandler.resetTimeout(); + if (mOnUiUpdate != null) { + mOnUiUpdate.run(); + } } }); mEnterAnimator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java new file mode 100644 index 000000000000..cece764580d1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ComponentName; +import android.content.Context; + +import com.android.systemui.R; + +import javax.inject.Inject; + +class ClipboardOverlayUtils { + + @Inject + ClipboardOverlayUtils() { + } + + boolean isRemoteCopy(Context context, ClipData clipData, String clipSource) { + if (clipData != null && clipData.getDescription().getExtras() != null + && clipData.getDescription().getExtras().getBoolean( + ClipDescription.EXTRA_IS_REMOTE_DEVICE)) { + ComponentName remoteComponent = ComponentName.unflattenFromString( + context.getResources().getString(R.string.config_remoteCopyPackage)); + if (remoteComponent != null) { + return remoteComponent.getPackageName().equals(clipSource); + } + } + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index bf7d71635694..6cb0e8b04869 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -24,7 +24,6 @@ import android.app.ActivityOptions import android.content.ComponentName import android.content.Context import android.content.Intent -import android.content.SharedPreferences import android.graphics.drawable.Drawable import android.graphics.drawable.LayerDrawable import android.service.controls.Control @@ -59,7 +58,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsPopupMenu import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy @@ -76,13 +78,14 @@ class ControlsUiControllerImpl @Inject constructor ( @Main val uiExecutor: DelayableExecutor, @Background val bgExecutor: DelayableExecutor, val controlsListingController: Lazy<ControlsListingController>, - @Main val sharedPreferences: SharedPreferences, val controlActionCoordinator: ControlActionCoordinator, private val activityStarter: ActivityStarter, private val shadeController: ShadeController, private val iconCache: CustomIconCache, private val controlsMetricsLogger: ControlsMetricsLogger, - private val keyguardStateController: KeyguardStateController + private val keyguardStateController: KeyguardStateController, + private val userFileManager: UserFileManager, + private val userTracker: UserTracker, ) : ControlsUiController { companion object { @@ -110,6 +113,12 @@ class ControlsUiControllerImpl @Inject constructor ( private lateinit var onDismiss: Runnable private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow) private var retainCache = false + private val sharedPreferences + get() = userFileManager.getSharedPreferences( + fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, + mode = 0, + userId = userTracker.userId + ) private val collator = Collator.getInstance(context.resources.configuration.locales[0]) private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 48bef97c30fb..2bee75e9435b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; import com.android.systemui.screenshot.ReferenceScreenshotModule; +import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; @@ -93,6 +94,7 @@ import dagger.Provides; AospPolicyModule.class, GestureModule.class, MediaModule.class, + MultiUserUtilsModule.class, PowerModule.class, QSModule.class, ReferenceScreenshotModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 6db562107357..482bdafc63cc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -58,7 +58,6 @@ import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.security.data.repository.SecurityRepositoryModule; -import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.ShadeController; import com.android.systemui.smartspace.dagger.SmartspaceModule; import com.android.systemui.statusbar.CommandQueue; @@ -140,7 +139,6 @@ import dagger.Provides; PrivacyModule.class, ScreenshotModule.class, SensorModule.class, - MultiUserUtilsModule.class, SecurityRepositoryModule.class, SettingsUtilModule.class, SmartRepliesInflationModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index fa499687fa21..2a94e5232b7c 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -63,7 +63,7 @@ object Flags { @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true) val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true) val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true) - @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, true) + @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, teamfood = true) // next id: 117 // 200 - keyguard/lockscreen @@ -122,7 +122,8 @@ object Flags { * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository * will occur in stages. This is one stage of many to come. */ - @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true) + // TODO(b/255607168): Tracking Bug + @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213) @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214) @@ -177,9 +178,9 @@ object Flags { @Deprecated("Replaced by mobile and wifi specific flags.") val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false) - val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606, false) + val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606) - val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607, false) + val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607) // 700 - dialer/calls // TODO(b/254512734): Tracking Bug @@ -229,7 +230,7 @@ object Flags { @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906) // TODO(b/254513168): Tracking Bug - val UMO_SURFACE_RIPPLE = UnreleasedFlag(907) + @JvmField val UMO_SURFACE_RIPPLE = UnreleasedFlag(907) // 1000 - dock val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000) @@ -238,11 +239,13 @@ object Flags { @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002) // 1100 - windowing + @JvmField @Keep val WM_ENABLE_SHELL_TRANSITIONS = SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false) /** b/170163464: animate bubbles expanded view collapse with home gesture */ + @JvmField @Keep val BUBBLES_HOME_GESTURE = SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true) @@ -264,40 +267,50 @@ object Flags { @Keep val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false) + @JvmField @Keep val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false) + @JvmField @Keep val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false) + @JvmField @Keep val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false) + @JvmField @Keep val SHOW_FLOATING_TASKS_AS_BUBBLES = SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false) + @JvmField @Keep val ENABLE_FLING_TO_DISMISS_BUBBLE = SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true) + @JvmField @Keep val ENABLE_FLING_TO_DISMISS_PIP = SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true) + @JvmField @Keep val ENABLE_PIP_KEEP_CLEAR_ALGORITHM = SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false) // 1200 - predictive back + @JvmField @Keep val WM_ENABLE_PREDICTIVE_BACK = SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true) + @JvmField @Keep val WM_ENABLE_PREDICTIVE_BACK_ANIM = SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false) + @JvmField @Keep val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK = SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false) @@ -307,7 +320,7 @@ object Flags { // 1300 - screenshots // TODO(b/254512719): Tracking Bug - @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300) + @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true) // TODO(b/254513155): Tracking Bug @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301) @@ -318,16 +331,17 @@ object Flags { // 1500 - chooser // TODO(b/254512507): Tracking Bug - val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, true) + val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true) // 1600 - accessibility @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600) // 1700 - clipboard - @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700) + @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true) + @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701) // 1800 - shade container - @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true) + @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, teamfood = true) // 1900 - note task @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt index 99ae85d7a548..80c6130955c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data import android.view.KeyEvent import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.plugins.ActivityStarter import java.lang.ref.WeakReference import javax.inject.Inject @@ -45,4 +46,9 @@ interface BouncerViewDelegate { fun dispatchBackKeyEventPreIme(): Boolean fun showNextSecurityScreenOrFinish(): Boolean fun resume() + fun setDismissAction( + onDismissAction: ActivityStarter.OnDismissAction?, + cancelAction: Runnable?, + ) + fun willDismissWithActions(): Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 543389e0a7cd..0046256c677b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -21,10 +21,9 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.ViewMediatorCallback import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel -import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN +import com.android.systemui.statusbar.phone.KeyguardBouncer import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -41,9 +40,15 @@ constructor( /** Determines if we want to instantaneously show the bouncer instead of translating. */ private val _isScrimmed = MutableStateFlow(false) val isScrimmed = _isScrimmed.asStateFlow() - /** Set amount of how much of the bouncer is showing on the screen */ - private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN) - val expansionAmount = _expansionAmount.asStateFlow() + /** + * Set how much of the panel is showing on the screen. + * ``` + * 0f = panel fully hidden = bouncer fully showing + * 1f = panel fully showing = bouncer fully hidden + * ``` + */ + private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN) + val panelExpansionAmount = _panelExpansionAmount.asStateFlow() private val _isVisible = MutableStateFlow(false) val isVisible = _isVisible.asStateFlow() private val _show = MutableStateFlow<KeyguardBouncerModel?>(null) @@ -54,8 +59,6 @@ constructor( val hide = _hide.asStateFlow() private val _startingToHide = MutableStateFlow(false) val startingToHide = _startingToHide.asStateFlow() - private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null) - val onDismissAction = _onDismissAction.asStateFlow() private val _disappearAnimation = MutableStateFlow<Runnable?>(null) val startingDisappearAnimation = _disappearAnimation.asStateFlow() private val _keyguardPosition = MutableStateFlow(0f) @@ -96,8 +99,8 @@ constructor( _isScrimmed.value = isScrimmed } - fun setExpansion(expansion: Float) { - _expansionAmount.value = expansion + fun setPanelExpansion(panelExpansion: Float) { + _panelExpansionAmount.value = panelExpansion } fun setVisible(isVisible: Boolean) { @@ -120,10 +123,6 @@ constructor( _startingToHide.value = startingToHide } - fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) { - _onDismissAction.value = bouncerCallbackActionsModel - } - fun setStartDisappearAnimation(runnable: Runnable?) { _disappearAnimation.value = runnable } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index b186ae0ceec4..6baaf5f10024 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -21,7 +21,10 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.doze.DozeHost +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject @@ -89,6 +92,9 @@ interface KeyguardRepository { /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> + /** Observable for device wake/sleep state */ + val wakefulnessState: Flow<WakefulnessModel> + /** * Returns `true` if the keyguard is showing; `false` otherwise. * @@ -118,6 +124,7 @@ constructor( statusBarStateController: StatusBarStateController, private val keyguardStateController: KeyguardStateController, dozeHost: DozeHost, + wakefulnessLifecycle: WakefulnessLifecycle, ) : KeyguardRepository { private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) override val animateBottomAreaDozingTransitions = @@ -207,6 +214,40 @@ constructor( awaitClose { statusBarStateController.removeCallback(callback) } } + override val wakefulnessState: Flow<WakefulnessModel> = conflatedCallbackFlow { + val callback = + object : WakefulnessLifecycle.Observer { + override fun onStartedWakingUp() { + trySendWithFailureLogging( + WakefulnessModel.STARTING_TO_WAKE, + TAG, + "Wakefulness: starting to wake" + ) + } + override fun onFinishedWakingUp() { + trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake") + } + override fun onStartedGoingToSleep() { + trySendWithFailureLogging( + WakefulnessModel.STARTING_TO_SLEEP, + TAG, + "Wakefulness: starting to sleep" + ) + } + override fun onFinishedGoingToSleep() { + trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep") + } + } + wakefulnessLifecycle.addObserver(callback) + trySendWithFailureLogging( + wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()), + TAG, + "initial wakefulness state" + ) + + awaitClose { wakefulnessLifecycle.removeObserver(callback) } + } + override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.value = animate } @@ -228,6 +269,16 @@ constructor( } } + private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel { + return when (value) { + 0 -> WakefulnessModel.ASLEEP + 1 -> WakefulnessModel.STARTING_TO_WAKE + 2 -> WakefulnessModel.AWAKE + 3 -> WakefulnessModel.STARTING_TO_SLEEP + else -> throw IllegalArgumentException("Invalid Wakefulness value: $value") + } + } + companion object { private const val TAG = "KeyguardRepositoryImpl" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index d15d7f25bbde..0c725208e22d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -22,4 +22,9 @@ import dagger.Module @Module interface KeyguardRepositoryModule { @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository + + @Binds + fun keyguardTransitionRepository( + impl: KeyguardTransitionRepositoryImpl + ): KeyguardTransitionRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index ab25597b077c..e3d1a27dad2b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -29,27 +29,33 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import java.util.UUID import javax.inject.Inject +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -@SysUISingleton -class KeyguardTransitionRepository @Inject constructor() { - /* - * Each transition between [KeyguardState]s will have an associated Flow. - * In order to collect these events, clients should call [transition]. - */ - private val _transitions = MutableStateFlow(TransitionStep()) - val transitions = _transitions.asStateFlow() - - /* Information about the active transition. */ - private var currentTransitionInfo: TransitionInfo? = null - /* - * When manual control of the transition is requested, a unique [UUID] is used as the handle - * to permit calls to [updateTransition] +/** + * The source of truth for all keyguard transitions. + * + * While the keyguard component is visible, it can undergo a number of transitions between different + * UI screens, such as AOD (Always-on Display), Bouncer, and others mentioned in [KeyguardState]. + * These UI elements should listen to events emitted by [transitions], to ensure a centrally + * coordinated experience. + * + * To create or modify logic that controls when and how transitions get created, look at + * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on + * this repository. + */ +interface KeyguardTransitionRepository { + /** + * All events regarding transitions, as they start, run, and complete. [TransitionStep#value] is + * a float between [0, 1] representing progress towards completion. If this is a user driven + * transition, that value may not be a monotonic progression, as the user may swipe in any + * direction. */ - private var updateTransitionId: UUID? = null + val transitions: Flow<TransitionStep> /** * Interactors that require information about changes between [KeyguardState]s will call this to @@ -60,22 +66,56 @@ class KeyguardTransitionRepository @Inject constructor() { } /** - * Begin a transition from one state to another. The [info.from] must match - * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid - * unplanned transitions. + * Begin a transition from one state to another. Will not start if another transition is in + * progress. + */ + fun startTransition(info: TransitionInfo): UUID? + + /** + * Allows manual control of a transition. When calling [startTransition], the consumer must pass + * in a null animator. In return, it will get a unique [UUID] that will be validated to allow + * further updates. + * + * When the transition is over, TransitionState.FINISHED must be passed into the [state] + * parameter. + */ + fun updateTransition( + transitionId: UUID, + @FloatRange(from = 0.0, to = 1.0) value: Float, + state: TransitionState + ) +} + +@SysUISingleton +class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitionRepository { + /* + * Each transition between [KeyguardState]s will have an associated Flow. + * In order to collect these events, clients should call [transition]. + */ + private val _transitions = + MutableSharedFlow<TransitionStep>( + extraBufferCapacity = 10, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + override val transitions = _transitions.asSharedFlow().distinctUntilChanged() + private var lastStep: TransitionStep = TransitionStep() + + /* + * When manual control of the transition is requested, a unique [UUID] is used as the handle + * to permit calls to [updateTransition] */ - fun startTransition(info: TransitionInfo): UUID? { - if (currentTransitionInfo != null) { + private var updateTransitionId: UUID? = null + + override fun startTransition(info: TransitionInfo): UUID? { + if (lastStep.transitionState != TransitionState.FINISHED) { // Open questions: // * Queue of transitions? buffer of 1? // * Are transitions cancellable if a new one is triggered? // * What validation does this need to do? - Log.wtf(TAG, "Transition still active: $currentTransitionInfo") + Log.wtf(TAG, "Transition still active: $lastStep") return null } - currentTransitionInfo?.animator?.cancel() - currentTransitionInfo = info info.animator?.let { animator -> // An animator was provided, so use it to run the transition animator.setFloatValues(0f, 1f) @@ -83,24 +123,24 @@ class KeyguardTransitionRepository @Inject constructor() { object : AnimatorUpdateListener { override fun onAnimationUpdate(animation: ValueAnimator) { emitTransition( - info, - (animation.getAnimatedValue() as Float), - TransitionState.RUNNING + TransitionStep( + info, + (animation.getAnimatedValue() as Float), + TransitionState.RUNNING + ) ) } } val adapter = object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator) { - Log.i(TAG, "Starting transition: $info") - emitTransition(info, 0f, TransitionState.STARTED) + emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) } override fun onAnimationCancel(animation: Animator) { Log.i(TAG, "Cancelling transition: $info") } override fun onAnimationEnd(animation: Animator) { - Log.i(TAG, "Ending transition: $info") - emitTransition(info, 1f, TransitionState.FINISHED) + emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED)) animator.removeListener(this) animator.removeUpdateListener(updateListener) } @@ -111,8 +151,7 @@ class KeyguardTransitionRepository @Inject constructor() { return@startTransition null } ?: run { - Log.i(TAG, "Starting transition (manual): $info") - emitTransition(info, 0f, TransitionState.STARTED) + emitTransition(TransitionStep(info, 0f, TransitionState.STARTED)) // No animator, so it's manual. Provide a mechanism to callback updateTransitionId = UUID.randomUUID() @@ -120,15 +159,7 @@ class KeyguardTransitionRepository @Inject constructor() { } } - /** - * Allows manual control of a transition. When calling [startTransition], the consumer must pass - * in a null animator. In return, it will get a unique [UUID] that will be validated to allow - * further updates. - * - * When the transition is over, TransitionState.FINISHED must be passed into the [state] - * parameter. - */ - fun updateTransition( + override fun updateTransition( transitionId: UUID, @FloatRange(from = 0.0, to = 1.0) value: Float, state: TransitionState @@ -138,52 +169,41 @@ class KeyguardTransitionRepository @Inject constructor() { return } - if (currentTransitionInfo == null) { - Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'") - return + if (state == TransitionState.FINISHED) { + updateTransitionId = null } - currentTransitionInfo?.let { info -> - if (state == TransitionState.FINISHED) { - updateTransitionId = null - Log.i(TAG, "Ending transition: $info") - } - - emitTransition(info, value, state) - } + val nextStep = lastStep.copy(value = value, transitionState = state) + emitTransition(nextStep, isManual = true) } - private fun emitTransition( - info: TransitionInfo, - value: Float, - transitionState: TransitionState - ) { - trace(info, transitionState) - - if (transitionState == TransitionState.FINISHED) { - currentTransitionInfo = null + private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) { + trace(nextStep, isManual) + val emitted = _transitions.tryEmit(nextStep) + if (!emitted) { + Log.w(TAG, "Failed to emit next value without suspending") } - _transitions.value = TransitionStep(info.from, info.to, value, transitionState) + lastStep = nextStep } - private fun trace(info: TransitionInfo, transitionState: TransitionState) { + private fun trace(step: TransitionStep, isManual: Boolean) { if ( - transitionState != TransitionState.STARTED && - transitionState != TransitionState.FINISHED + step.transitionState != TransitionState.STARTED && + step.transitionState != TransitionState.FINISHED ) { return } val traceName = - "Transition: ${info.from} -> ${info.to} " + - if (info.animator == null) { + "Transition: ${step.from} -> ${step.to} " + + if (isManual) { "(manual)" } else { "" } val traceCookie = traceName.hashCode() - if (transitionState == TransitionState.STARTED) { + if (step.transitionState == TransitionState.STARTED) { Trace.beginAsyncSection(traceName, traceCookie) - } else if (transitionState == TransitionState.FINISHED) { + } else if (step.transitionState == TransitionState.FINISHED) { Trace.endAsyncSection(traceName, traceCookie) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt index 400376663f1a..0aeff7fc69fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collect @@ -36,31 +37,35 @@ constructor( @Application private val scope: CoroutineScope, private val keyguardRepository: KeyguardRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) : TransitionInteractor("AOD<->LOCKSCREEN") { override fun start() { scope.launch { - keyguardRepository.isDozing.collect { isDozing -> - if (isDozing) { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.LOCKSCREEN, - KeyguardState.AOD, - getAnimator(), + keyguardRepository.isDozing + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (isDozing, keyguardState) = pair + if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.AOD, + getAnimator(), + ) ) - ) - } else { - keyguardTransitionRepository.startTransition( - TransitionInfo( - name, - KeyguardState.AOD, - KeyguardState.LOCKSCREEN, - getAnimator(), + } else if (!isDozing && keyguardState == KeyguardState.AOD) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.AOD, + KeyguardState.LOCKSCREEN, + getAnimator(), + ) ) - ) + } } - } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt index 2af9318d92ec..dbb0352c2187 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt @@ -30,7 +30,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository -import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.plugins.ActivityStarter @@ -40,6 +39,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map @@ -77,7 +77,7 @@ constructor( KeyguardBouncerModel( promptReason = repository.bouncerPromptReason ?: 0, errorMessage = repository.bouncerErrorMessage, - expansionAmount = repository.expansionAmount.value + expansionAmount = repository.panelExpansionAmount.value ) ) repository.setShowingSoon(false) @@ -90,14 +90,22 @@ constructor( val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {} val isVisible: Flow<Boolean> = repository.isVisible val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull() - val expansionAmount: Flow<Float> = repository.expansionAmount val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull() val startingDisappearAnimation: Flow<Runnable> = repository.startingDisappearAnimation.filterNotNull() - val onDismissAction: Flow<BouncerCallbackActionsModel> = - repository.onDismissAction.filterNotNull() val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it } val keyguardPosition: Flow<Float> = repository.keyguardPosition + val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount + /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */ + val bouncerExpansion: Flow<Float> = // + combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible + -> + if (isVisible) { + 1f - expansionAmount + } else { + 0f + } + } // TODO(b/243685699): Move isScrimmed logic to data layer. // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. @@ -128,7 +136,7 @@ constructor( Trace.beginSection("KeyguardBouncer#show") repository.setScrimmed(isScrimmed) if (isScrimmed) { - setExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE) } if (resumeBouncer) { @@ -149,7 +157,6 @@ constructor( } keyguardStateController.notifyBouncerShowing(true) callbackInteractor.dispatchStartingToShow() - Trace.endSection() } @@ -168,7 +175,6 @@ constructor( keyguardStateController.notifyBouncerShowing(false /* showing */) cancelShowRunnable() repository.setShowingSoon(false) - repository.setOnDismissAction(null) repository.setVisible(false) repository.setHide(true) repository.setShow(null) @@ -176,14 +182,17 @@ constructor( } /** - * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f - * where 0f => showing and 1f => hiding + * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f + * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the + * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show. + * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion + * of 0f represents the bouncer fully showing. */ - fun setExpansion(expansion: Float) { - val oldExpansion = repository.expansionAmount.value + fun setPanelExpansion(expansion: Float) { + val oldExpansion = repository.panelExpansionAmount.value val expansionChanged = oldExpansion != expansion if (repository.startingDisappearAnimation.value == null) { - repository.setExpansion(expansion) + repository.setPanelExpansion(expansion) } if ( @@ -227,7 +236,7 @@ constructor( onDismissAction: ActivityStarter.OnDismissAction?, cancelAction: Runnable? ) { - repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction)) + bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction) } /** Update the resources of the views. */ @@ -282,7 +291,7 @@ constructor( /** Returns whether bouncer is fully showing. */ fun isFullyShowing(): Boolean { return (repository.showingSoon.value || repository.isVisible.value) && - repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE && + repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE && repository.startingDisappearAnimation.value == null } @@ -294,8 +303,8 @@ constructor( /** If bouncer expansion is between 0f and 1f non-inclusive. */ fun isInTransit(): Boolean { return repository.showingSoon.value || - repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN && - repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE + repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN && + repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE } /** Return whether bouncer is animating away. */ @@ -305,7 +314,7 @@ constructor( /** Return whether bouncer will dismiss with actions */ fun willDismissWithAction(): Boolean { - return repository.onDismissAction.value?.onDismissAction != null + return bouncerView.delegate?.willDismissWithActions() == true } /** Returns whether the bouncer should be full screen. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt new file mode 100644 index 000000000000..0e2a54c57bdb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class GoneAodTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, +) : TransitionInteractor("GONE->AOD") { + + override fun start() { + scope.launch { + keyguardInteractor.wakefulnessState + .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) + .collect { pair -> + val (wakefulnessState, keyguardState) = pair + if ( + keyguardState == KeyguardState.GONE && + wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP + ) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.GONE, + KeyguardState.AOD, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 500L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index fc2269c6b01c..03c6a789326e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.WakefulnessModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -40,6 +41,8 @@ constructor( val isDozing: Flow<Boolean> = repository.isDozing /** Whether the keyguard is showing to not. */ val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing + /** The device wake/sleep state */ + val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState fun isKeyguardShowing(): Boolean { return repository.isKeyguardShowing() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt new file mode 100644 index 000000000000..83d94325b9d4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import com.android.keyguard.logging.KeyguardLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +/** Collect flows of interest for auditing keyguard transitions. */ +@SysUISingleton +class KeyguardTransitionAuditLogger +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val interactor: KeyguardTransitionInteractor, + private val keyguardInteractor: KeyguardInteractor, + private val logger: KeyguardLogger, +) { + + fun start() { + scope.launch { + keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) } + } + + scope.launch { + interactor.finishedKeyguardState.collect { logger.i("Finished transition to", it) } + } + + scope.launch { + interactor.startedKeyguardState.collect { logger.i("Started transition to", it) } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index b166681433a8..d5ea77b8e729 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -26,6 +26,7 @@ class KeyguardTransitionCoreStartable @Inject constructor( private val interactors: Set<TransitionInteractor>, + private val auditLogger: KeyguardTransitionAuditLogger, ) : CoreStartable { override fun start() { @@ -38,9 +39,12 @@ constructor( when (it) { is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it") is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it") + is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it") + is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it") } it.start() } + auditLogger.start() } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 7409aec57b4c..dffd097a77c5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -19,11 +19,15 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -40,6 +44,9 @@ constructor( /** LOCKSCREEN->AOD transition information. */ val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD) + /** GONE->AOD information. */ + val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD) + /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). @@ -49,4 +56,16 @@ constructor( aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) }, lockscreenToAodTransition, ) + + /* The last completed [KeyguardState] transition */ + val finishedKeyguardState: Flow<KeyguardState> = + repository.transitions + .filter { step -> step.transitionState == TransitionState.FINISHED } + .map { step -> step.to } + + /* The last started [KeyguardState] transition */ + val startedKeyguardState: Flow<KeyguardState> = + repository.transitions + .filter { step -> step.transitionState == TransitionState.STARTED } + .map { step -> step.to } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt index 3c2a12e3836a..761f3fd9d9f9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch @@ -40,59 +41,63 @@ constructor( private val keyguardRepository: KeyguardRepository, private val shadeRepository: ShadeRepository, private val keyguardTransitionRepository: KeyguardTransitionRepository, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor ) : TransitionInteractor("LOCKSCREEN<->BOUNCER") { private var transitionId: UUID? = null override fun start() { scope.launch { - shadeRepository.shadeModel.sample( - combine( - keyguardTransitionRepository.transitions, - keyguardRepository.statusBarState, - ) { transitions, statusBarState -> - Pair(transitions, statusBarState) - } - ) { shadeModel, pair -> - val (transitions, statusBarState) = pair + shadeRepository.shadeModel + .sample( + combine( + keyguardTransitionInteractor.finishedKeyguardState, + keyguardRepository.statusBarState, + ) { keyguardState, statusBarState -> + Pair(keyguardState, statusBarState) + }, + { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) } + ) + .collect { triple -> + val (shadeModel, keyguardState, statusBarState) = triple - val id = transitionId - if (id != null) { - // An existing `id` means a transition is started, and calls to - // `updateTransition` will control it until FINISHED - keyguardTransitionRepository.updateTransition( - id, - shadeModel.expansionAmount, - if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) { - transitionId = null - TransitionState.FINISHED - } else { - TransitionState.RUNNING - } - ) - } else { - // TODO (b/251849525): Remove statusbarstate check when that state is integrated - // into KeyguardTransitionRepository - val isOnLockscreen = - transitions.transitionState == TransitionState.FINISHED && - transitions.to == KeyguardState.LOCKSCREEN - if ( - isOnLockscreen && - shadeModel.isUserDragging && - statusBarState != SHADE_LOCKED - ) { - transitionId = - keyguardTransitionRepository.startTransition( - TransitionInfo( - ownerName = name, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.BOUNCER, - animator = null, + val id = transitionId + if (id != null) { + // An existing `id` means a transition is started, and calls to + // `updateTransition` will control it until FINISHED + keyguardTransitionRepository.updateTransition( + id, + shadeModel.expansionAmount, + if ( + shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f + ) { + transitionId = null + TransitionState.FINISHED + } else { + TransitionState.RUNNING + } + ) + } else { + // TODO (b/251849525): Remove statusbarstate check when that state is + // integrated + // into KeyguardTransitionRepository + if ( + keyguardState == KeyguardState.LOCKSCREEN && + shadeModel.isUserDragging && + statusBarState != SHADE_LOCKED + ) { + transitionId = + keyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = name, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.BOUNCER, + animator = null, + ) ) - ) + } } } - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt new file mode 100644 index 000000000000..6c1adbd68ef2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.animation.ValueAnimator +import com.android.systemui.animation.Interpolators +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch + +@SysUISingleton +class LockscreenGoneTransitionInteractor +@Inject +constructor( + @Application private val scope: CoroutineScope, + private val keyguardInteractor: KeyguardInteractor, + private val keyguardTransitionRepository: KeyguardTransitionRepository, +) : TransitionInteractor("LOCKSCREEN->GONE") { + + override fun start() { + scope.launch { + keyguardInteractor.isKeyguardShowing.collect { isShowing -> + if (!isShowing) { + keyguardTransitionRepository.startTransition( + TransitionInfo( + name, + KeyguardState.LOCKSCREEN, + KeyguardState.GONE, + getAnimator(), + ) + ) + } + } + } + } + + private fun getAnimator(): ValueAnimator { + return ValueAnimator().apply { + setInterpolator(Interpolators.LINEAR) + setDuration(TRANSITION_DURATION_MS) + } + } + + companion object { + private const val TRANSITION_DURATION_MS = 10L + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index 74c542c0043f..728bafae2a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -39,4 +39,10 @@ abstract class StartKeyguardTransitionModule { @Binds @IntoSet abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor + + @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor + + @Binds + @IntoSet + abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index f66d5d3650c8..7958033ba017 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -17,7 +17,10 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { - /** For initialization only */ + /** + * For initialization as well as when the security method is set to NONE, indicating that + * the keyguard should never be shown. + */ NONE, /* Always-on Display. The device is in a low-power mode with a minimal UI visible */ AOD, @@ -31,4 +34,11 @@ enum class KeyguardState { * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. */ LOCKSCREEN, + + /* + * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard + * is being removed, but there are other cases where the user is swiping away keyguard, such as + * with SWIPE security method or face unlock without bypass. + */ + GONE, } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt index d8691c17f53d..0e0465bb5207 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.shared.model /** Possible states for a running transition between [State] */ enum class TransitionState { - NONE, STARTED, RUNNING, FINISHED diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 688ec912aac8..0ca358210813 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -20,5 +20,11 @@ data class TransitionStep( val from: KeyguardState = KeyguardState.NONE, val to: KeyguardState = KeyguardState.NONE, val value: Float = 0f, // constrained [0.0, 1.0] - val transitionState: TransitionState = TransitionState.NONE, -) + val transitionState: TransitionState = TransitionState.FINISHED, +) { + constructor( + info: TransitionInfo, + value: Float, + transitionState: TransitionState, + ) : this(info.from, info.to, value, transitionState) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt new file mode 100644 index 000000000000..64f834d6c5ab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.keyguard.shared.model + +/** Model device wakefulness states. */ +enum class WakefulnessModel { + /** The device is asleep and not interactive. */ + ASLEEP, + /** Received a signal that the device is beginning to wake up. */ + STARTING_TO_WAKE, + /** Device is now fully awake and interactive. */ + AWAKE, + /** Signal that the device is now going to sleep. */ + STARTING_TO_SLEEP, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index df260148751c..a22958b74bb9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -29,6 +29,7 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.collect @@ -75,6 +76,17 @@ object KeyguardBouncerViewBinder { hostViewController.showPrimarySecurityScreen() hostViewController.onResume() } + + override fun setDismissAction( + onDismissAction: ActivityStarter.OnDismissAction?, + cancelAction: Runnable? + ) { + hostViewController.setOnDismissAction(onDismissAction, cancelAction) + } + + override fun willDismissWithActions(): Boolean { + return hostViewController.hasDismissActions() + } } view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -122,15 +134,6 @@ object KeyguardBouncerViewBinder { } launch { - viewModel.setDismissAction.collect { - hostViewController.setOnDismissAction( - it.onDismissAction, - it.cancelAction - ) - } - } - - launch { viewModel.startDisappearAnimation.collect { hostViewController.startDisappearAnimation(it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index 9ad52117bfc6..9a9284371074 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -20,7 +20,6 @@ import android.view.View import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.domain.interactor.BouncerInteractor -import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE @@ -38,7 +37,7 @@ constructor( private val interactor: BouncerInteractor, ) { /** Observe on bouncer expansion amount. */ - val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount + val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount /** Observe on bouncer visibility. */ val isBouncerVisible: Flow<Boolean> = interactor.isVisible @@ -63,9 +62,6 @@ constructor( /** Observe whether bouncer is starting to hide. */ val startingToHide: Flow<Unit> = interactor.startingToHide - /** Observe whether we want to set the dismiss action to the bouncer. */ - val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction - /** Observe whether we want to start the disappear animation. */ val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt index ed649b1c0265..f006442906e7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt @@ -181,6 +181,7 @@ constructor( return enabled == other.enabled && name == other.name && intent == other.intent && - id == other.id + id == other.id && + showBroadcastButton == other.showBroadcastButton } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt index 2511324a943e..a7f1b95555ba 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt @@ -26,6 +26,7 @@ import android.widget.TextView import androidx.constraintlayout.widget.Barrier import com.android.systemui.R import com.android.systemui.media.controls.models.GutsViewHolder +import com.android.systemui.ripple.MultiRippleView import com.android.systemui.util.animation.TransitionLayout private const val TAG = "MediaViewHolder" @@ -36,6 +37,7 @@ class MediaViewHolder constructor(itemView: View) { // Player information val albumView = itemView.requireViewById<ImageView>(R.id.album_art) + val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view) val appIcon = itemView.requireViewById<ImageView>(R.id.icon) val titleText = itemView.requireViewById<TextView>(R.id.header_title) val artistText = itemView.requireViewById<TextView>(R.id.header_artist) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt index 61ef2f1838e7..918417fcd9a9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt @@ -29,6 +29,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.settingslib.Utils import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.monet.ColorScheme +import com.android.systemui.ripple.MultiRippleController /** * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme] @@ -100,12 +101,14 @@ class ColorSchemeTransition internal constructor( private val context: Context, private val mediaViewHolder: MediaViewHolder, + private val multiRippleController: MultiRippleController, animatingColorTransitionFactory: AnimatingColorTransitionFactory ) { constructor( context: Context, - mediaViewHolder: MediaViewHolder - ) : this(context, mediaViewHolder, ::AnimatingColorTransition) + mediaViewHolder: MediaViewHolder, + multiRippleController: MultiRippleController, + ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition) val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95) val surfaceColor = @@ -125,6 +128,7 @@ internal constructor( val accentColorList = ColorStateList.valueOf(accentPrimary) mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary) + multiRippleController.updateColor(accentPrimary) } val accentSecondary = 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 18ecadb28cf3..5b14cf34827a 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 @@ -76,6 +76,8 @@ import com.android.systemui.bluetooth.BroadcastDialogController; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.media.controls.models.GutsViewHolder; import com.android.systemui.media.controls.models.player.MediaAction; import com.android.systemui.media.controls.models.player.MediaButton; @@ -95,6 +97,10 @@ import com.android.systemui.monet.ColorScheme; import com.android.systemui.monet.Style; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.ripple.MultiRippleController; +import com.android.systemui.ripple.RippleAnimation; +import com.android.systemui.ripple.RippleAnimationConfig; +import com.android.systemui.ripple.RippleShader; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -209,6 +215,8 @@ public class MediaControlPanel { private boolean mIsCurrentBroadcastedApp = false; private boolean mShowBroadcastDialogButton = false; private String mSwitchBroadcastApp; + private MultiRippleController mMultiRippleController; + private FeatureFlags mFeatureFlags; /** * Initialize a new control panel @@ -236,7 +244,9 @@ public class MediaControlPanel { KeyguardStateController keyguardStateController, ActivityIntentHelper activityIntentHelper, NotificationLockscreenUserManager lockscreenUserManager, - BroadcastDialogController broadcastDialogController) { + BroadcastDialogController broadcastDialogController, + FeatureFlags featureFlags + ) { mContext = context; mBackgroundExecutor = backgroundExecutor; mMainExecutor = mainExecutor; @@ -262,6 +272,8 @@ public class MediaControlPanel { logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT); return Unit.INSTANCE; }); + + mFeatureFlags = featureFlags; } /** @@ -381,7 +393,9 @@ public class MediaControlPanel { AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit, Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText); - mColorSchemeTransition = new ColorSchemeTransition(mContext, mMediaViewHolder); + mMultiRippleController = new MultiRippleController(vh.getMultiRippleView()); + mColorSchemeTransition = new ColorSchemeTransition( + mContext, mMediaViewHolder, mMultiRippleController); mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter); } @@ -982,6 +996,9 @@ public class MediaControlPanel { mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId); logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT); action.run(); + if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) { + mMultiRippleController.play(createTouchRippleAnimation(button)); + } if (icon instanceof Animatable) { ((Animatable) icon).start(); @@ -997,6 +1014,26 @@ public class MediaControlPanel { } } + private RippleAnimation createTouchRippleAnimation(ImageButton button) { + float maxSize = mMediaViewHolder.getMultiRippleView().getWidth() * 2; + return new RippleAnimation( + new RippleAnimationConfig( + RippleShader.RippleShape.CIRCLE, + /* duration= */ 1500L, + /* centerX= */ button.getX() + button.getWidth() * 0.5f, + /* centerY= */ button.getY() + button.getHeight() * 0.5f, + /* maxWidth= */ maxSize, + /* maxHeight= */ maxSize, + /* pixelDensity= */ getContext().getResources().getDisplayMetrics().density, + mColorSchemeTransition.getAccentPrimary().getTargetColor(), + /* opacity= */ 100, + /* shouldFillRipple= */ false, + /* sparkleStrength= */ 0f, + /* shouldDistort= */ false + ) + ); + } + private void clearButton(final ImageButton button) { button.setImageDrawable(null); button.setContentDescription(null); diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt index 7dd9fb4b9cd5..662d059aedcd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt @@ -189,7 +189,7 @@ class MediaTttChipControllerReceiver @Inject constructor( } private fun startRipple(rippleView: ReceiverChipRippleView) { - if (rippleView.rippleInProgress) { + if (rippleView.rippleInProgress()) { // Skip if ripple is still playing return } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 50cf63d52909..813d2a0ac4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -91,7 +91,11 @@ import android.view.InsetsVisibilities; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import android.view.View; +import android.view.ViewRootImpl; +import android.view.ViewRootImpl.SurfaceChangedCallback; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; @@ -366,15 +370,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @Override - public void onQuickStepStarted() { - // Use navbar dragging as a signal to hide the rotate button - mView.getRotationButtonController().setRotateSuggestionButtonState(false); - - // Hide the notifications panel when quick step starts - mShadeController.collapsePanel(true /* animate */); - } - - @Override public void onPrioritizedRotation(@Surface.Rotation int rotation) { mStartingQuickSwitchRotation = rotation; if (rotation == -1) { @@ -487,6 +482,24 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } }; + private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback = + new SurfaceChangedCallback() { + @Override + public void surfaceCreated(Transaction t) { + notifyNavigationBarSurface(); + } + + @Override + public void surfaceDestroyed() { + notifyNavigationBarSurface(); + } + + @Override + public void surfaceReplaced(Transaction t) { + notifyNavigationBarSurface(); + } + }; + @Inject NavigationBar( NavigationBarView navigationBarView, @@ -696,7 +709,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements final Display display = mView.getDisplay(); mView.setComponents(mRecentsOptional); if (mCentralSurfacesOptionalLazy.get().isPresent()) { - mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController()); + mView.setComponents( + mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController()); } mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer); mView.setOnVerticalChangedListener(this::onVerticalChanged); @@ -717,6 +731,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.getViewTreeObserver().addOnComputeInternalInsetsListener( mOnComputeInternalInsetsListener); + mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback); + notifyNavigationBarSurface(); mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); @@ -795,6 +811,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mHandler.removeCallbacks(mEnableLayoutTransitions); mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener); + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback); + } mFrame = null; mOrientationHandle = null; } @@ -947,6 +967,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } } + private void notifyNavigationBarSurface() { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null; + mOverviewProxyService.onNavigationBarSurfaceChanged(surface); + } + private int deltaRotation(int oldRotation, int newRotation) { int delta = newRotation - oldRotation; if (delta < 0) delta += 4; @@ -1272,8 +1298,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } private void onVerticalChanged(boolean isVertical) { - mCentralSurfacesOptionalLazy.get().ifPresent( - statusBar -> statusBar.setQsScrimEnabled(!isVertical)); + mCentralSurfacesOptionalLazy.get().ifPresent(statusBar -> + statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical)); } private boolean onNavigationTouch(View v, MotionEvent event) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index 622f5a279a5f..83c2a5de5c6e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -412,10 +412,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface { logSomePresses(action, flags); if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) { Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action)); - if (action == MotionEvent.ACTION_UP) { - mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0, - -1, -1, true /* isButton */, false /* gestureSwipeLeft */); - } } final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, 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 10ff48babe16..7964d164dc3a 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -289,8 +289,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mBackAnimation.setTriggerBack(true); } - mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x, - (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); logGesture(mInRejectedExclusion ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED); @@ -302,8 +300,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mBackAnimation.setTriggerBack(false); } logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE); - mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x, - (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge); } @Override @@ -804,9 +800,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker if (mExcludeRegion.contains(x, y)) { if (withinRange) { - // Log as exclusion only if it is in acceptable range in the first place. - mOverviewProxyService.notifyBackAction( - false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge); // We don't have the end point for logging purposes. mEndPoint.x = -1; mEndPoint.y = -1; diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java index 703b95a082dc..b5ceeaed4904 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java +++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java @@ -19,6 +19,7 @@ package com.android.systemui.qs.carrier; import android.annotation.StyleRes; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; @@ -33,6 +34,7 @@ import com.android.settingslib.Utils; import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; +import com.android.systemui.util.LargeScreenUtils; import java.util.Objects; @@ -72,6 +74,7 @@ public class QSCarrier extends LinearLayout { mMobileSignal = findViewById(R.id.mobile_signal); mCarrierText = findViewById(R.id.qs_carrier_text); mSpacer = findViewById(R.id.spacer); + updateResources(); } /** @@ -142,4 +145,20 @@ public class QSCarrier extends LinearLayout { public void updateTextAppearance(@StyleRes int resId) { FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId); } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateResources(); + } + + private void updateResources() { + boolean useLargeScreenHeader = + LargeScreenUtils.shouldUseLargeScreenShadeHeader(getResources()); + mCarrierText.setMaxEms( + useLargeScreenHeader + ? Integer.MAX_VALUE + : getResources().getInteger(R.integer.qs_carrier_max_em) + ); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index e9a6c25c0e6d..1f92b12c1a83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -140,7 +140,7 @@ public class QSIconViewImpl extends QSIconView { iv.setTag(R.id.qs_icon_tag, icon); iv.setTag(R.id.qs_slash_tag, state.slash); iv.setPadding(0, padding, 0, padding); - if (d instanceof Animatable2) { + if (shouldAnimate && d instanceof Animatable2) { Animatable2 a = (Animatable2) d; a.start(); if (state.isTransient) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 66be00d8de66..46c4f410d078 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -64,6 +64,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; +import android.view.SurfaceControl; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; @@ -149,6 +150,7 @@ public class OverviewProxyService extends CurrentUserTracker implements private final UiEventLogger mUiEventLogger; private Region mActiveNavBarRegion; + private SurfaceControl mNavigationBarSurface; private IOverviewProxy mOverviewProxy; private int mConnectionBackoffAttempts; @@ -190,7 +192,8 @@ public class OverviewProxyService extends CurrentUserTracker implements // TODO move this logic to message queue mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> { if (event.getActionMasked() == ACTION_DOWN) { - centralSurfaces.getPanelController().startExpandLatencyTracking(); + centralSurfaces.getNotificationPanelViewController() + .startExpandLatencyTracking(); } mHandler.post(() -> { int action = event.getActionMasked(); @@ -217,17 +220,15 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void onBackPressed() throws RemoteException { + public void onBackPressed() { verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> { sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); - - notifyBackAction(true, -1, -1, true, false); }); } @Override - public void onImeSwitcherPressed() throws RemoteException { + public void onImeSwitcherPressed() { // TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since // Launcher/Taskbar isn't display aware. mContext.getSystemService(InputMethodManager.class) @@ -316,12 +317,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override - public void notifySwipeUpGestureStarted() { - verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () -> - notifySwipeUpGestureStartedInternal()); - } - - @Override public void notifyPrioritizedRotation(@Surface.Rotation int rotation) { verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () -> notifyPrioritizedRotationInternal(rotation)); @@ -443,6 +438,7 @@ public class OverviewProxyService extends CurrentUserTracker implements Log.e(TAG_OPS, "Failed to call onInitialize()", e); } dispatchNavButtonBounds(); + dispatchNavigationBarSurface(); // Force-update the systemui state flags updateSystemUiStateFlags(); @@ -597,11 +593,18 @@ public class OverviewProxyService extends CurrentUserTracker implements .commitUpdate(mContext.getDisplayId()); } - public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton, - boolean gestureSwipeLeft) { + /** + * Called when the navigation bar surface is created or changed + */ + public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) { + mNavigationBarSurface = navbarSurface; + dispatchNavigationBarSurface(); + } + + private void dispatchNavigationBarSurface() { try { if (mOverviewProxy != null) { - mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft); + mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface); } } catch (RemoteException e) { Log.e(TAG_OPS, "Failed to notify back action", e); @@ -614,7 +617,7 @@ public class OverviewProxyService extends CurrentUserTracker implements final NavigationBarView navBarView = mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId()); final NotificationPanelViewController panelController = - mCentralSurfacesOptionalLazy.get().get().getPanelController(); + mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController(); if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment + " navBarView=" + navBarView + " panelController=" + panelController); @@ -800,24 +803,12 @@ public class OverviewProxyService extends CurrentUserTracker implements } } - public void notifyQuickStepStarted() { - for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { - mConnectionCallbacks.get(i).onQuickStepStarted(); - } - } - private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onPrioritizedRotation(rotation); } } - public void notifyQuickScrubStarted() { - for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { - mConnectionCallbacks.get(i).onQuickScrubStarted(); - } - } - private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onAssistantProgress(progress); @@ -836,12 +827,6 @@ public class OverviewProxyService extends CurrentUserTracker implements } } - private void notifySwipeUpGestureStartedInternal() { - for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { - mConnectionCallbacks.get(i).onSwipeUpGestureStarted(); - } - } - public void notifyAssistantVisibilityChanged(float visibility) { try { if (mOverviewProxy != null) { @@ -1005,23 +990,20 @@ public class OverviewProxyService extends CurrentUserTracker implements pw.print(" mWindowCornerRadius="); pw.println(mWindowCornerRadius); pw.print(" mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows); pw.print(" mActiveNavBarRegion="); pw.println(mActiveNavBarRegion); + pw.print(" mNavigationBarSurface="); pw.println(mNavigationBarSurface); pw.print(" mNavBarMode="); pw.println(mNavBarMode); mSysUiState.dump(pw, args); } public interface OverviewProxyListener { default void onConnectionChanged(boolean isConnected) {} - default void onQuickStepStarted() {} - default void onSwipeUpGestureStarted() {} default void onPrioritizedRotation(@Surface.Rotation int rotation) {} default void onOverviewShown(boolean fromHome) {} - default void onQuickScrubStarted() {} /** Notify the recents app (overview) is started by 3-button navigation. */ default void onToggleRecentApps() {} default void onHomeRotationEnabled(boolean enabled) {} default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {} default void onTaskbarAutohideSuspend(boolean suspend) {} - default void onSystemUiStateChanged(int sysuiStateFlags) {} default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {} default void onAssistantGestureCompletion(float velocity) {} default void startAssistant(Bundle bundle) {} diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt new file mode 100644 index 000000000000..48df15c78ea5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.ripple + +import androidx.annotation.VisibleForTesting + +/** Controller that handles playing [RippleAnimation]. */ +class MultiRippleController(private val multipleRippleView: MultiRippleView) { + + companion object { + /** Max number of ripple animations at a time. */ + @VisibleForTesting const val MAX_RIPPLE_NUMBER = 10 + } + + /** Updates all the ripple colors during the animation. */ + fun updateColor(color: Int) { + multipleRippleView.ripples.forEach { anim -> anim.updateColor(color) } + } + + fun play(rippleAnimation: RippleAnimation) { + if (multipleRippleView.ripples.size >= MAX_RIPPLE_NUMBER) { + return + } + + multipleRippleView.ripples.add(rippleAnimation) + + // Remove ripple once the animation is done + rippleAnimation.play { multipleRippleView.ripples.remove(rippleAnimation) } + + // Trigger drawing + multipleRippleView.invalidate() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt new file mode 100644 index 000000000000..c7f0b7e0056e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.ripple + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.util.Log +import android.view.View + +/** + * A view that allows multiple ripples to play. + * + * Use [MultiRippleController] to play ripple animations. + */ +class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { + + internal val ripples = ArrayList<RippleAnimation>() + private val ripplePaint = Paint() + private var isWarningLogged = false + + companion object { + const val TAG = "MultiRippleView" + } + + override fun onDraw(canvas: Canvas?) { + if (canvas == null || !canvas.isHardwareAccelerated) { + // Drawing with the ripple shader requires hardware acceleration, so skip + // if it's unsupported. + if (!isWarningLogged) { + // Only log once to not spam. + Log.w( + TAG, + "Can't draw ripple shader. $canvas does not support hardware acceleration." + ) + isWarningLogged = true + } + return + } + + var shouldInvalidate = false + + ripples.forEach { anim -> + ripplePaint.shader = anim.rippleShader + canvas.drawPaint(ripplePaint) + + shouldInvalidate = shouldInvalidate || anim.isPlaying() + } + + if (shouldInvalidate) invalidate() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt new file mode 100644 index 000000000000..aca9e254e4c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.ripple + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import androidx.core.graphics.ColorUtils + +/** A single ripple animation. */ +class RippleAnimation(private val config: RippleAnimationConfig) { + internal val rippleShader: RippleShader = RippleShader(config.rippleShape) + private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f) + + init { + applyConfigToShader() + } + + /** Updates the ripple color during the animation. */ + fun updateColor(color: Int) { + config.apply { config.color = color } + applyConfigToShader() + } + + @JvmOverloads + fun play(onAnimationEnd: Runnable? = null) { + if (isPlaying()) { + return // Ignore if ripple effect is already playing + } + + animator.duration = config.duration + animator.addUpdateListener { updateListener -> + val now = updateListener.currentPlayTime + val progress = updateListener.animatedValue as Float + rippleShader.progress = progress + rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f + rippleShader.time = now.toFloat() + } + animator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.run() + } + } + ) + animator.start() + } + + /** Indicates whether the animation is playing. */ + fun isPlaying(): Boolean = animator.isRunning + + private fun applyConfigToShader() { + rippleShader.setCenter(config.centerX, config.centerY) + rippleShader.setMaxSize(config.maxWidth, config.maxHeight) + rippleShader.rippleFill = config.shouldFillRipple + rippleShader.pixelDensity = config.pixelDensity + rippleShader.color = ColorUtils.setAlphaComponent(config.color, config.opacity) + rippleShader.sparkleStrength = config.sparkleStrength + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt new file mode 100644 index 000000000000..88122544c7cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt @@ -0,0 +1,32 @@ +package com.android.systemui.ripple + +import android.graphics.Color + +/** + * A struct that holds the ripple animation configurations. + * + * <p>This configuration is designed to play a SINGLE animation. Do not reuse or modify the + * configuration parameters to play different animations, unless the value has to change within the + * single animation (e.g. Change color or opacity during the animation). Note that this data class + * is pulled out to make the [RippleAnimation] constructor succinct. + */ +data class RippleAnimationConfig( + val rippleShape: RippleShader.RippleShape = RippleShader.RippleShape.CIRCLE, + val duration: Long = 0L, + val centerX: Float = 0f, + val centerY: Float = 0f, + val maxWidth: Float = 0f, + val maxHeight: Float = 0f, + val pixelDensity: Float = 1f, + var color: Int = Color.WHITE, + val opacity: Int = RIPPLE_DEFAULT_ALPHA, + val shouldFillRipple: Boolean = false, + val sparkleStrength: Float = RIPPLE_SPARKLE_STRENGTH, + val shouldDistort: Boolean = true +) { + companion object { + const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f + const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() + const val RIPPLE_DEFAULT_ALPHA: Int = 45 // full opacity is 255. + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt index 1e51ffa292b7..a6d79303962f 100644 --- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt @@ -28,10 +28,6 @@ import android.view.View import androidx.core.graphics.ColorUtils import com.android.systemui.ripple.RippleShader.RippleShape -private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f -private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt() -const val RIPPLE_DEFAULT_ALPHA: Int = 45 - /** * A generic expanding ripple effect. * @@ -45,8 +41,8 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a private set private val ripplePaint = Paint() + private val animator = ValueAnimator.ofFloat(0f, 1f) - var rippleInProgress: Boolean = false var duration: Long = 1750 private var maxWidth: Float = 0.0f @@ -80,9 +76,9 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a this.rippleShape = rippleShape rippleShader = RippleShader(rippleShape) - rippleShader.color = RIPPLE_DEFAULT_COLOR + rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR rippleShader.progress = 0f - rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH + rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH rippleShader.pixelDensity = resources.displayMetrics.density ripplePaint.shader = rippleShader @@ -90,10 +86,9 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { - if (rippleInProgress) { + if (animator.isRunning) { return // Ignore if ripple effect is already playing } - val animator = ValueAnimator.ofFloat(0f, 1f) animator.duration = duration animator.addUpdateListener { updateListener -> val now = updateListener.currentPlayTime @@ -105,19 +100,17 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a } animator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { - rippleInProgress = false onAnimationEnd?.run() } }) animator.start() - rippleInProgress = true } /** Set the color to be used for the ripple. * * The alpha value of the color will be applied to the ripple. The alpha range is [0-100]. */ - fun setColor(color: Int, alpha: Int = RIPPLE_DEFAULT_ALPHA) { + fun setColor(color: Int, alpha: Int = RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA) { rippleShader.color = ColorUtils.setAlphaComponent(color, alpha) } @@ -137,6 +130,9 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a rippleShader.sparkleStrength = strength } + /** Indicates whether the ripple animation is playing. */ + fun rippleInProgress(): Boolean = animator.isRunning + override fun onDraw(canvas: Canvas?) { if (canvas == null || !canvas.isHardwareAccelerated) { // Drawing with the ripple shader requires hardware acceleration, so skip diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java index e3658defc52a..c8c133774766 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java @@ -38,6 +38,8 @@ import androidx.concurrent.futures.CallbackToFutureAdapter; import androidx.exifinterface.media.ExifInterface; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.google.common.util.concurrent.ListenableFuture; @@ -85,10 +87,12 @@ class ImageExporter { private final ContentResolver mResolver; private CompressFormat mCompressFormat = CompressFormat.PNG; private int mQuality = 100; + private final FeatureFlags mFlags; @Inject - ImageExporter(ContentResolver resolver) { + ImageExporter(ContentResolver resolver, FeatureFlags flags) { mResolver = resolver; + mFlags = flags; } /** @@ -161,7 +165,7 @@ class ImageExporter { ZonedDateTime captureTime, UserHandle owner) { final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat, - mQuality, /* publish */ true, owner); + mQuality, /* publish */ true, owner, mFlags); return CallbackToFutureAdapter.getFuture( (completer) -> { @@ -209,9 +213,11 @@ class ImageExporter { private final UserHandle mOwner; private final String mFileName; private final boolean mPublish; + private final FeatureFlags mFlags; Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime, - CompressFormat format, int quality, boolean publish, UserHandle owner) { + CompressFormat format, int quality, boolean publish, UserHandle owner, + FeatureFlags flags) { mResolver = resolver; mRequestId = requestId; mBitmap = bitmap; @@ -221,6 +227,7 @@ class ImageExporter { mOwner = owner; mFileName = createFilename(mCaptureTime, mFormat); mPublish = publish; + mFlags = flags; } public Result execute() throws ImageExportException, InterruptedException { @@ -234,7 +241,7 @@ class ImageExporter { start = Instant.now(); } - uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner); + uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags); throwIfInterrupted(); writeImage(mResolver, mBitmap, mFormat, mQuality, uri); @@ -278,13 +285,15 @@ class ImageExporter { } private static Uri createEntry(ContentResolver resolver, CompressFormat format, - ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException { + ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags) + throws ImageExportException { Trace.beginSection("ImageExporter_createEntry"); try { final ContentValues values = createMetadata(time, format, fileName); Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - if (UserHandle.myUserId() != owner.getIdentifier()) { + if (flags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY) + && UserHandle.myUserId() != owner.getIdentifier()) { baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier()); } Uri uri = resolver.insert(baseUri, values); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index d524a356a323..9b5295d1bb3d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -1043,8 +1043,13 @@ public class ScreenshotController { } private boolean isUserSetupComplete(UserHandle owner) { - return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) - .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) { + return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) + .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } else { + return Settings.Secure.getInt(mContext.getContentResolver(), + SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 671173413e73..cd5647e51029 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -30,7 +30,6 @@ import androidx.annotation.GuardedBy import androidx.annotation.WorkerThread import com.android.systemui.Dumpable import com.android.systemui.dump.DumpManager -import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE import com.android.systemui.util.Assert import java.io.PrintWriter import java.lang.ref.WeakReference @@ -53,7 +52,7 @@ import kotlin.reflect.KProperty * * Class constructed and initialized in [SettingsModule]. */ -class UserTrackerImpl internal constructor( +open class UserTrackerImpl internal constructor( private val context: Context, private val userManager: UserManager, private val dumpManager: DumpManager, @@ -70,13 +69,13 @@ class UserTrackerImpl internal constructor( private val mutex = Any() override var userId: Int by SynchronizedDelegate(context.userId) - private set + protected set override var userHandle: UserHandle by SynchronizedDelegate(context.user) - private set + protected set override var userContext: Context by SynchronizedDelegate(context) - private set + protected set override val userContentResolver: ContentResolver get() = userContext.contentResolver @@ -94,7 +93,7 @@ class UserTrackerImpl internal constructor( * modified. */ override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList()) - private set + protected set @GuardedBy("callbacks") private val callbacks: MutableList<DataItem> = ArrayList() @@ -155,7 +154,7 @@ class UserTrackerImpl internal constructor( } @WorkerThread - private fun handleSwitchUser(newUser: Int) { + protected open fun handleSwitchUser(newUser: Int) { Assert.isNotMainThread() if (newUser == UserHandle.USER_NULL) { Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent") @@ -174,7 +173,7 @@ class UserTrackerImpl internal constructor( } @WorkerThread - private fun handleProfilesChanged() { + protected open fun handleProfilesChanged() { Assert.isNotMainThread() val profiles = userManager.getProfiles(userId) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 1c0f05745280..3a624ca7b6ab 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -33,7 +33,6 @@ import static com.android.systemui.classifier.Classifier.GENERIC; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; import static com.android.systemui.classifier.Classifier.UNLOCK; -import static com.android.systemui.shade.NotificationPanelView.DEBUG; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING; @@ -41,9 +40,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; -import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED; import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD; import static com.android.systemui.util.DumpUtilsKt.asIndenting; @@ -53,10 +50,10 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Fragment; import android.app.StatusBarManager; import android.content.ContentResolver; -import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Canvas; @@ -104,7 +101,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.Interpolator; import android.widget.FrameLayout; -import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintSet; import com.android.internal.annotations.VisibleForTesting; @@ -175,16 +171,13 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; -import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -210,7 +203,6 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent; -import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; @@ -257,28 +249,15 @@ public final class NotificationPanelViewController { private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG); private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private static final boolean DEBUG_DRAWABLE = false; - private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT = VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false); - - /** - * The parallax amount of the quick settings translation when dragging down the panel - */ + /** The parallax amount of the quick settings translation when dragging down the panel. */ private static final float QS_PARALLAX_AMOUNT = 0.175f; - - /** - * Fling expanding QS. - */ + /** Fling expanding QS. */ public static final int FLING_EXPAND = 0; - - /** - * Fling collapsing QS, potentially stopping when QS becomes QQS. - */ + /** Fling collapsing QS, potentially stopping when QS becomes QQS. */ private static final int FLING_COLLAPSE = 1; - - /** - * Fling until QS is completely hidden. - */ + /** Fling until QS is completely hidden. */ private static final int FLING_HIDE = 2; private static final long ANIMATION_DELAY_ICON_FADE_IN = ActivityLaunchAnimator.TIMINGS.getTotalDuration() @@ -292,6 +271,18 @@ public final class NotificationPanelViewController { * when flinging. A low value will make it that most flings will reach the maximum overshoot. */ private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f; + /** + * Maximum time before which we will expand the panel even for slow motions when getting a + * touch passed over from launcher. + */ + private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300; + private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50; + private static final String COUNTER_PANEL_OPEN = "panel_open"; + private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; + private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; + private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1); + private static final Rect EMPTY_RECT = new Rect(); + private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager; private final Resources mResources; private final KeyguardStateController mKeyguardStateController; @@ -300,49 +291,24 @@ public final class NotificationPanelViewController { private final LockscreenGestureLogger mLockscreenGestureLogger; private final SystemClock mSystemClock; private final ShadeLogger mShadeLog; - private final DozeParameters mDozeParameters; - private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener(); - private final Runnable mCollapseExpandAction = new CollapseExpandAction(); - private final OnOverscrollTopChangedListener - mOnOverscrollTopChangedListener = - new OnOverscrollTopChangedListener(); - private final OnEmptySpaceClickListener - mOnEmptySpaceClickListener = - new OnEmptySpaceClickListener(); - private final MyOnHeadsUpChangedListener - mOnHeadsUpChangedListener = - new MyOnHeadsUpChangedListener(); - private final HeightListener mHeightListener = new HeightListener(); + private final Runnable mCollapseExpandAction = this::collapseOrExpand; + private final NsslOverscrollTopChangedListener mOnOverscrollTopChangedListener = + new NsslOverscrollTopChangedListener(); + private final NotificationStackScrollLayout.OnEmptySpaceClickListener + mOnEmptySpaceClickListener = (x, y) -> onEmptySpaceClick(); + private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener = + new ShadeHeadsUpChangedListener(); + private final QS.HeightListener mHeightListener = this::onQsHeightChanged; private final ConfigurationListener mConfigurationListener = new ConfigurationListener(); private final SettingsChangeObserver mSettingsChangeObserver; - - @VisibleForTesting - final StatusBarStateListener mStatusBarStateListener = - new StatusBarStateListener(); + private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener(); private final NotificationPanelView mView; private final VibratorHelper mVibratorHelper; private final MetricsLogger mMetricsLogger; private final ConfigurationController mConfigurationController; private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; - private final NotificationIconAreaController mNotificationIconAreaController; - - /** - * Maximum time before which we will expand the panel even for slow motions when getting a - * touch passed over from launcher. - */ - private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300; - - private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50; - - private static final String COUNTER_PANEL_OPEN = "panel_open"; - private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; - private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; - - private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1); - private static final Rect EMPTY_RECT = new Rect(); - private final InteractionJankMonitor mInteractionJankMonitor; private final LayoutInflater mLayoutInflater; private final FeatureFlags mFeatureFlags; @@ -362,9 +328,7 @@ public final class NotificationPanelViewController { private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; private final FragmentService mFragmentService; private final ScrimController mScrimController; - private final PrivacyDotViewController mPrivacyDotViewController; private final NotificationRemoteInputManager mRemoteInputManager; - private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final ShadeTransitionController mShadeTransitionController; private final TapAgainViewController mTapAgainViewController; @@ -381,6 +345,11 @@ public final class NotificationPanelViewController { private final Interpolator mBounceInterpolator; private final NotificationShadeWindowController mNotificationShadeWindowController; private final ShadeExpansionStateManager mShadeExpansionStateManager; + private final QS.ScrollListener mQsScrollListener = this::onQsPanelScrollChanged; + private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired; + private final FragmentListener mQsFragmentListener = new QsFragmentListener(); + private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate(); + private long mDownTime; private boolean mTouchSlopExceededBeforeDown; private boolean mIsLaunchAnimationRunning; @@ -402,13 +371,11 @@ public final class NotificationPanelViewController { private float mKeyguardNotificationTopPadding; /** Current max allowed keyguard notifications determined by measuring the panel. */ private int mMaxAllowedKeyguardNotifications; - private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController; private KeyguardUserSwitcherController mKeyguardUserSwitcherController; private KeyguardStatusBarView mKeyguardStatusBar; private KeyguardStatusBarViewController mKeyguardStatusBarViewController; - @VisibleForTesting - QS mQs; + private QS mQs; private FrameLayout mQsFrame; private final QsFrameTranslateController mQsFrameTranslateController; private KeyguardStatusViewController mKeyguardStatusViewController; @@ -421,18 +388,11 @@ public final class NotificationPanelViewController { private float mQuickQsHeaderHeight; private final ScreenOffAnimationController mScreenOffAnimationController; private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - private int mQsTrackingPointer; private VelocityTracker mQsVelocityTracker; private boolean mQsTracking; - - /** - * If set, the ongoing touch gesture might both trigger the expansion in {@link - * NotificationPanelView} and - * the expansion for quick settings. - */ + /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */ private boolean mConflictingQsExpansionGesture; - private boolean mPanelExpanded; /** @@ -487,11 +447,9 @@ public final class NotificationPanelViewController { * Used for split shade, two finger gesture as well as accessibility shortcut to QS. * It needs to be set when movement starts as it resets at the end of expansion/collapse. */ - @VisibleForTesting - boolean mQsExpandImmediate; + private boolean mQsExpandImmediate; private boolean mTwoFingerQsExpandPossible; private String mHeaderDebugInfo; - /** * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still * need to take this into account in our panel height calculation. @@ -499,7 +457,6 @@ public final class NotificationPanelViewController { private boolean mQsAnimatorExpand; private boolean mIsLaunchTransitionFinished; private ValueAnimator mQsSizeChangeAnimator; - private boolean mQsScrimEnabled = true; private boolean mQsTouchAboveFalsingThreshold; private int mQsFalsingThreshold; @@ -517,39 +474,27 @@ public final class NotificationPanelViewController { private final FalsingManager mFalsingManager; private final FalsingCollector mFalsingCollector; - private final Runnable mHeadsUpExistenceChangedRunnable = () -> { - setHeadsUpAnimatingAway(false); - updatePanelExpansionAndVisibility(); - }; private boolean mShowIconsWhenExpanded; private int mIndicationBottomPadding; private int mAmbientIndicationBottomPadding; + /** Whether the notifications are displayed full width (no margins on the side). */ private boolean mIsFullWidth; private boolean mBlockingExpansionForCurrentTouch; + // Following variables maintain state of events when input focus transfer may occur. + private boolean mExpectingSynthesizedDown; + private boolean mLastEventSynthesizedDown; - /** - * Following variables maintain state of events when input focus transfer may occur. - */ - private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event - private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event - - /** - * Current dark amount that follows regular interpolation curve of animation. - */ + /** Current dark amount that follows regular interpolation curve of animation. */ private float mInterpolatedDarkAmount; - /** * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the * interpolation curve is different. */ private float mLinearDarkAmount; - private boolean mPulsing; private boolean mHideIconsDuringLaunchAnimation = true; private int mStackScrollerMeasuringPass; - /** - * Non-null if there's a heads-up notification that we're currently tracking the position of. - */ + /** Non-null if a heads-up notification's position is being tracked. */ @Nullable private ExpandableNotificationRow mTrackedHeadsUpNotification; private final ArrayList<Consumer<ExpandableNotificationRow>> @@ -579,17 +524,19 @@ public final class NotificationPanelViewController { private final CommandQueue mCommandQueue; private final UserManager mUserManager; private final MediaDataManager mMediaDataManager; + @PanelState + private int mCurrentPanelState = STATE_CLOSED; private final SysUiState mSysUiState; - private final NotificationShadeDepthController mDepthController; private final NavigationBarController mNavigationBarController; private final int mDisplayId; - private KeyguardIndicationController mKeyguardIndicationController; + private final KeyguardIndicationController mKeyguardIndicationController; private int mHeadsUpInset; private boolean mHeadsUpPinnedMode; private boolean mAllowExpandForSmallExpansion; private Runnable mExpandAfterLayoutRunnable; + private Runnable mHideExpandedRunnable; /** * The padding between the start of notifications and the qs boundary on the lockscreen. @@ -597,94 +544,51 @@ public final class NotificationPanelViewController { * qs boundary to be padded. */ private int mLockscreenNotificationQSPadding; - /** * The amount of progress we are currently in if we're transitioning to the full shade. * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full * shade. This value can also go beyond 1.1 when we're overshooting! */ private float mTransitioningToFullShadeProgress; - /** * Position of the qs bottom during the full shade transition. This is needed as the toppadding * can change during state changes, which makes it much harder to do animations */ private int mTransitionToFullShadeQSPosition; - - /** - * Distance that the full shade transition takes in order for qs to fully transition to the - * shade. - */ + /** Distance a full shade transition takes in order for qs to fully transition to the shade. */ private int mDistanceForQSFullShadeTransition; - - /** - * The translation amount for QS for the full shade transition - */ + /** The translation amount for QS for the full shade transition. */ private float mQsTranslationForFullShadeTransition; - /** - * The maximum overshoot allowed for the top padding for the full shade transition - */ + /** The maximum overshoot allowed for the top padding for the full shade transition. */ private int mMaxOverscrollAmountForPulse; - - /** - * Should we animate the next bounds update - */ + /** Should we animate the next bounds update. */ private boolean mAnimateNextNotificationBounds; - /** - * The delay for the next bounds animation - */ + /** The delay for the next bounds animation. */ private long mNotificationBoundsAnimationDelay; - - /** - * The duration of the notification bounds animation - */ + /** The duration of the notification bounds animation. */ private long mNotificationBoundsAnimationDuration; - /** - * Is this a collapse that started on the panel where we should allow the panel to intercept - */ + /** Whether a collapse that started on the panel should allow the panel to intercept. */ private boolean mIsPanelCollapseOnQQS; - private boolean mAnimatingQS; - - /** - * The end bounds of a clipping animation. - */ + /** The end bounds of a clipping animation. */ private final Rect mQsClippingAnimationEndBounds = new Rect(); - - /** - * The animator for the qs clipping bounds. - */ + /** The animator for the qs clipping bounds. */ private ValueAnimator mQsClippingAnimation = null; - - /** - * Is the current animator resetting the qs translation. - */ + /** Whether the current animator is resetting the qs translation. */ private boolean mIsQsTranslationResetAnimator; - /** - * Is the current animator resetting the pulse expansion after a drag down - */ + /** Whether the current animator is resetting the pulse expansion after a drag down. */ private boolean mIsPulseExpansionResetAnimator; private final Rect mKeyguardStatusAreaClipBounds = new Rect(); private final Region mQsInterceptRegion = new Region(); - - /** - * The alpha of the views which only show on the keyguard but not in shade / shade locked - */ + /** Alpha of the views which only show on the keyguard but not in shade / shade locked. */ private float mKeyguardOnlyContentAlpha = 1.0f; - - /** - * The translationY of the views which only show on the keyguard but in shade / shade locked. - */ + /** Y translation of the views that only show on the keyguard but in shade / shade locked. */ private int mKeyguardOnlyTransitionTranslationY = 0; - private float mUdfpsMaxYBurnInOffset; - - /** - * Are we currently in gesture navigation - */ + /** Are we currently in gesture navigation. */ private boolean mIsGestureNavigation; private int mOldLayoutDirection; private NotificationShelfController mNotificationShelfController; @@ -697,6 +601,7 @@ public final class NotificationPanelViewController { private int mQsClipTop; private int mQsClipBottom; private boolean mQsVisible; + private final ContentResolver mContentResolver; private float mMinFraction; @@ -715,55 +620,7 @@ public final class NotificationPanelViewController { private final NotificationListContainer mNotificationListContainer; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; - private final NPVCDownEventState.Buffer mLastDownEvents; - - private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = - () -> mKeyguardBottomArea.setVisibility(View.GONE); - - private final AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { - @Override - public void onInitializeAccessibilityNodeInfo(View host, - AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(host, info); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); - } - - @Override - public boolean performAccessibilityAction(View host, int action, Bundle args) { - if (action - == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId() - || action - == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) { - mStatusBarKeyguardViewManager.showBouncer(true); - return true; - } - return super.performAccessibilityAction(host, action, args); - } - }; - - private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() { - @Override - public void onAdditionalTapRequired() { - if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { - mTapAgainViewController.show(); - } else { - mKeyguardIndicationController.showTransientIndication( - R.string.notification_tap_again); - } - - if (!mStatusBarStateController.isDozing()) { - mVibratorHelper.vibrate( - Process.myUid(), - mView.getContext().getPackageName(), - ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT, - "falsing-additional-tap-required", - TOUCH_VIBRATION_ATTRIBUTES); - } - } - }; - private final CameraGestureHelper mCameraGestureHelper; private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; @@ -813,8 +670,20 @@ public final class NotificationPanelViewController { private boolean mGestureWaitForTouchSlop; private boolean mIgnoreXTouchSlop; private boolean mExpandLatencyTracking; + private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); + private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = + () -> mKeyguardBottomArea.setVisibility(View.GONE); + private final Runnable mHeadsUpExistenceChangedRunnable = () -> { + setHeadsUpAnimatingAway(false); + updatePanelExpansionAndVisibility(); + }; + private final Runnable mMaybeHideExpandedRunnable = () -> { + if (getExpansionFraction() == 0.0f) { + getView().post(mHideExpandedRunnable); + } + }; @Inject public NotificationPanelViewController(NotificationPanelView view, @@ -849,7 +718,6 @@ public final class NotificationPanelViewController { KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory, KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory, LockscreenShadeTransitionController lockscreenShadeTransitionController, - NotificationIconAreaController notificationIconAreaController, AuthController authController, ScrimController scrimController, UserManager userManager, @@ -858,7 +726,6 @@ public final class NotificationPanelViewController { AmbientState ambientState, LockIconViewController lockIconViewController, KeyguardMediaController keyguardMediaController, - PrivacyDotViewController privacyDotViewController, TapAgainViewController tapAgainViewController, NavigationModeController navigationModeController, NavigationBarController navigationBarController, @@ -876,6 +743,7 @@ public final class NotificationPanelViewController { SysUiState sysUiState, Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider, KeyguardUnlockAnimationController keyguardUnlockAnimationController, + KeyguardIndicationController keyguardIndicationController, NotificationListContainer notificationListContainer, NotificationStackSizeCalculator notificationStackSizeCalculator, UnlockedScreenOffAnimationController unlockedScreenOffAnimationController, @@ -896,7 +764,6 @@ public final class NotificationPanelViewController { mLockscreenGestureLogger = lockscreenGestureLogger; mShadeExpansionStateManager = shadeExpansionStateManager; mShadeLog = shadeLogger; - TouchHandler touchHandler = createTouchHandler(); mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { @@ -904,16 +771,16 @@ public final class NotificationPanelViewController { } @Override - public void onViewDetachedFromWindow(View v) { - } + public void onViewDetachedFromWindow(View v) {} }); - mView.addOnLayoutChangeListener(createLayoutChangeListener()); - mView.setOnTouchListener(touchHandler); - mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener()); + mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener()); + mView.setOnTouchListener(createTouchHandler()); + mView.setOnConfigurationChangedListener(config -> loadDimens()); mResources = mView.getResources(); mKeyguardStateController = keyguardStateController; + mKeyguardIndicationController = keyguardIndicationController; mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; mNotificationShadeWindowController = notificationShadeWindowController; FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get(); @@ -946,7 +813,6 @@ public final class NotificationPanelViewController { mInteractionJankMonitor = interactionJankMonitor; mSystemClock = systemClock; mKeyguardMediaController = keyguardMediaController; - mPrivacyDotViewController = privacyDotViewController; mMetricsLogger = metricsLogger; mConfigurationController = configurationController; mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder; @@ -958,7 +824,6 @@ public final class NotificationPanelViewController { mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider; mNotificationsQSContainerController.init(); mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; - mNotificationIconAreaController = notificationIconAreaController; mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory; mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory; mDepthController = notificationShadeDepthController; @@ -1001,10 +866,7 @@ public final class NotificationPanelViewController { mShadeTransitionController = shadeTransitionController; lockscreenShadeTransitionController.setNotificationPanelController(this); shadeTransitionController.setNotificationPanelViewController(this); - DynamicPrivacyControlListener - dynamicPrivacyControlListener = - new DynamicPrivacyControlListener(); - dynamicPrivacyController.addListener(dynamicPrivacyControlListener); + dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged); shadeExpansionStateManager.addStateListener(this::onPanelStateChanged); @@ -1028,13 +890,14 @@ public final class NotificationPanelViewController { mIsGestureNavigation = QuickStepContract.isGesturalMode(currentMode); mView.setBackgroundColor(Color.TRANSPARENT); - OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener(); + ShadeAttachStateChangeListener + onAttachStateChangeListener = new ShadeAttachStateChangeListener(); mView.addOnAttachStateChangeListener(onAttachStateChangeListener); if (mView.isAttachedToWindow()) { onAttachStateChangeListener.onViewAttachedToWindow(mView); } - mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener()); + mView.setOnApplyWindowInsetsListener((v, insets) -> onApplyShadeWindowInsets(insets)); if (DEBUG_DRAWABLE) { mView.getOverlay().add(new DebugDrawable()); @@ -1053,57 +916,68 @@ public final class NotificationPanelViewController { new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() { @Override public void onUnlockAnimationFinished() { - // Make sure the clock is in the correct position after the unlock animation - // so that it's not in the wrong place when we show the keyguard again. - positionClockAndNotifications(true /* forceClockUpdate */); + unlockAnimationFinished(); } @Override public void onUnlockAnimationStarted( boolean playingCannedAnimation, boolean isWakeAndUnlock, - long unlockAnimationStartDelay, + long startDelay, long unlockAnimationDuration) { - // Disable blurs while we're unlocking so that panel expansion does not - // cause blurring. This will eventually be re-enabled by the panel view on - // ACTION_UP, since the user's finger might still be down after a swipe to - // unlock gesture, and we don't want that to cause blurring either. - mDepthController.setBlursDisabledForUnlock(mTracking); - - if (playingCannedAnimation && !isWakeAndUnlock) { - // Hide the panel so it's not in the way or the surface behind the - // keyguard, which will be appearing. If we're wake and unlocking, the - // lock screen is hidden instantly so should not be flung away. - if (isTracking() || isFlinging()) { - // Instant collpase the notification panel since the notification - // panel is already in the middle animating - onTrackingStopped(false); - instantCollapse(); - } else { - mView.animate() - .alpha(0f) - .setStartDelay(0) - // Translate up by 4%. - .translationY(mView.getHeight() * -0.04f) - // This start delay is to give us time to animate out before - // the launcher icons animation starts, so use that as our - // duration. - .setDuration(unlockAnimationStartDelay) - .setInterpolator(EMPHASIZED_ACCELERATE) - .withEndAction(() -> { - instantCollapse(); - mView.setAlpha(1f); - mView.setTranslationY(0f); - }) - .start(); - } - } + unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay); } }); mCameraGestureHelper = cameraGestureHelper; mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor; } + private void unlockAnimationFinished() { + // Make sure the clock is in the correct position after the unlock animation + // so that it's not in the wrong place when we show the keyguard again. + positionClockAndNotifications(true /* forceClockUpdate */); + } + + private void unlockAnimationStarted( + boolean playingCannedAnimation, + boolean isWakeAndUnlock, + long unlockAnimationStartDelay) { + // Disable blurs while we're unlocking so that panel expansion does not + // cause blurring. This will eventually be re-enabled by the panel view on + // ACTION_UP, since the user's finger might still be down after a swipe to + // unlock gesture, and we don't want that to cause blurring either. + mDepthController.setBlursDisabledForUnlock(mTracking); + + if (playingCannedAnimation && !isWakeAndUnlock) { + // Hide the panel so it's not in the way or the surface behind the + // keyguard, which will be appearing. If we're wake and unlocking, the + // lock screen is hidden instantly so should not be flung away. + if (isTracking() || mIsFlinging) { + // Instant collapse the notification panel since the notification + // panel is already in the middle animating + onTrackingStopped(false); + instantCollapse(); + } else { + mView.animate() + .alpha(0f) + .setStartDelay(0) + // Translate up by 4%. + .translationY(mView.getHeight() * -0.04f) + // This start delay is to give us time to animate out before + // the launcher icons animation starts, so use that as our + // duration. + .setDuration(unlockAnimationStartDelay) + .setInterpolator(EMPHASIZED_ACCELERATE) + .withEndAction(() -> { + instantCollapse(); + mView.setAlpha(1f); + mView.setTranslationY(0f); + }) + .start(); + } + } + } + @VisibleForTesting void onFinishInflate() { loadDimens(); @@ -1140,7 +1014,7 @@ public final class NotificationPanelViewController { R.id.notification_stack_scroller); mNotificationStackScrollLayoutController.attach(stackScrollLayout); mNotificationStackScrollLayoutController.setOnHeightChangedListener( - mOnHeightChangedListener); + new NsslHeightChangedListener()); mNotificationStackScrollLayoutController.setOverscrollTopChangedListener( mOnOverscrollTopChangedListener); mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled); @@ -1148,7 +1022,7 @@ public final class NotificationPanelViewController { mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener( mOnEmptySpaceClickListener); addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp); - mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area); + setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area)); initBottomArea(); @@ -1261,11 +1135,6 @@ public final class NotificationPanelViewController { } } - private void setCentralSurfaces(CentralSurfaces centralSurfaces) { - // TODO: this can be injected. - mCentralSurfaces = centralSurfaces; - } - public void updateResources() { mSplitShadeNotificationsScrimMarginBottom = mResources.getDimensionPixelSize( @@ -1351,7 +1220,7 @@ public final class NotificationPanelViewController { @VisibleForTesting void reInflateViews() { - if (DEBUG_LOGCAT) Log.d(TAG, "reInflateViews"); + debugLog("reInflateViews"); // Re-inflate the status view group. KeyguardStatusView keyguardStatusView = mNotificationContainerParent.findViewById(R.id.keyguard_status_view); @@ -1397,7 +1266,7 @@ public final class NotificationPanelViewController { int index = mView.indexOfChild(mKeyguardBottomArea); mView.removeView(mKeyguardBottomArea); KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea; - mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView(); + setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView()); mKeyguardBottomArea.initFrom(oldBottomArea); mView.addView(mKeyguardBottomArea, index); initBottomArea(); @@ -1430,6 +1299,11 @@ public final class NotificationPanelViewController { mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView)); } + @VisibleForTesting + void setQs(QS qs) { + mQs = qs; + } + private void attachSplitShadeMediaPlayerContainer(FrameLayout container) { mKeyguardMediaController.attachSplitShadeContainer(container); } @@ -1444,12 +1318,7 @@ public final class NotificationPanelViewController { } @VisibleForTesting - boolean getClosing() { - return mClosing; - } - - @VisibleForTesting - boolean getIsFlinging() { + boolean isFlinging() { return mIsFlinging; } @@ -1476,8 +1345,8 @@ public final class NotificationPanelViewController { return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying(); } - public void setKeyguardIndicationController(KeyguardIndicationController indicationController) { - mKeyguardIndicationController = indicationController; + private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) { + mKeyguardBottomArea = keyguardBottomArea; mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea); } @@ -1924,13 +1793,13 @@ public final class NotificationPanelViewController { setQsExpandImmediate(true); setShowShelfOnly(true); } - if (DEBUG) this.logf("collapse: " + this); + debugLog("collapse: %s", this); if (canPanelBeCollapsed()) { cancelHeightAnimator(); notifyExpandingStarted(); // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state. - setIsClosing(true); + setClosing(true); if (delayed) { mNextCollapseSpeedUpFactor = speedUpFactor; this.mView.postDelayed(mFlingCollapseRunnable, 120); @@ -1940,13 +1809,19 @@ public final class NotificationPanelViewController { } } - private void setQsExpandImmediate(boolean expandImmediate) { + @VisibleForTesting + void setQsExpandImmediate(boolean expandImmediate) { if (expandImmediate != mQsExpandImmediate) { mQsExpandImmediate = expandImmediate; mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate); } } + @VisibleForTesting + boolean isQsExpandImmediate() { + return mQsExpandImmediate; + } + private void setShowShelfOnly(boolean shelfOnly) { mNotificationStackScrollLayoutController.setShouldShowShelfOnly( shelfOnly && !mSplitShadeEnabled); @@ -1954,7 +1829,7 @@ public final class NotificationPanelViewController { public void closeQs() { cancelQsAnimation(); - setQsExpansion(mQsMinExpansionHeight); + setQsExpansionHeight(mQsMinExpansionHeight); } @VisibleForTesting @@ -1992,7 +1867,7 @@ public final class NotificationPanelViewController { } float height = mQsExpansionHeight; mQsExpansionAnimator.cancel(); - setQsExpansion(height); + setQsExpansionHeight(height); } flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE); } @@ -2016,7 +1891,7 @@ public final class NotificationPanelViewController { // case but currently motion in portrait looks worse than when using flingSettings. // TODO: make below function transitioning smoothly also in portrait with null target mLockscreenShadeTransitionController.goToLockedShade( - /* expandedView= */null, /* needsQSAnimation= */false); + /* expandedView= */null, /* needsQSAnimation= */true); } else if (isFullyCollapsed()) { expand(true /* animate */); } else { @@ -2033,12 +1908,12 @@ public final class NotificationPanelViewController { } } - public void fling(float vel, boolean expand) { + private void fling(float vel) { GestureRecorder gr = mCentralSurfaces.getGestureRecorder(); if (gr != null) { gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } - fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false); + fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false); } @VisibleForTesting @@ -2125,7 +2000,7 @@ public final class NotificationPanelViewController { @Override public void onAnimationEnd(Animator animation) { if (shouldSpringBack && !mCancelled) { - // After the shade is flinged open to an overscrolled state, spring back + // After the shade is flung open to an overscrolled state, spring back // the shade by reducing section padding to 0. springBack(); } else { @@ -2155,7 +2030,7 @@ public final class NotificationPanelViewController { } private boolean onQsIntercept(MotionEvent event) { - if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept"); + debugLog("onQsIntercept"); int pointerIndex = event.findPointerIndex(mQsTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -2205,7 +2080,7 @@ public final class NotificationPanelViewController { // Already tracking because onOverscrolled was called. We need to update here // so we don't stop for a frame until the next touch event gets handled in // onTouchEvent. - setQsExpansion(h + mInitialHeightOnTouch); + setQsExpansionHeight(h + mInitialHeightOnTouch); trackMovement(event); return true; } else { @@ -2216,7 +2091,7 @@ public final class NotificationPanelViewController { if ((h > touchSlop || (h < -touchSlop && mQsExpanded)) && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { - if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion"); + debugLog("onQsIntercept - start tracking expansion"); mView.getParent().requestDisallowInterceptTouchEvent(true); mShadeLog.onQsInterceptMoveQsTrackingEnabled(h); mQsTracking = true; @@ -2275,7 +2150,7 @@ public final class NotificationPanelViewController { private void initDownStates(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { mQsTouchAboveFalsingThreshold = mQsFullyExpanded; - mDozingOnDown = isDozing(); + mDozingOnDown = mDozing; mDownX = event.getX(); mDownY = event.getY(); mCollapsedOnDown = isFullyCollapsed(); @@ -2325,7 +2200,7 @@ public final class NotificationPanelViewController { float vel = getCurrentQSVelocity(); boolean expandsQs = flingExpandsQs(vel); if (expandsQs) { - if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) { + if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) { expandsQs = false; } else { logQsSwipeDown(y); @@ -2364,9 +2239,9 @@ public final class NotificationPanelViewController { } } - private boolean isFalseTouch(@Classifier.InteractionType int interactionType) { + private boolean isFalseTouch() { if (mFalsingManager.isClassifierEnabled()) { - return mFalsingManager.isFalseTouch(interactionType); + return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS); } return !mQsTouchAboveFalsingThreshold; } @@ -2492,7 +2367,7 @@ public final class NotificationPanelViewController { private void handleQsDown(MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept( event.getX(), event.getY(), -1)) { - if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown"); + debugLog("handleQsDown"); mFalsingCollector.onQsDown(); mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled"); mQsTracking = true; @@ -2506,9 +2381,7 @@ public final class NotificationPanelViewController { } } - /** - * Input focus transfer is about to happen. - */ + /** Input focus transfer is about to happen. */ public void startWaitingForOpenPanelGesture() { if (!isFullyCollapsed()) { return; @@ -2540,7 +2413,7 @@ public final class NotificationPanelViewController { } else { // Window never will receive touch events that typically trigger haptic on open. maybeVibrateOnOpening(false /* openingWithTouch */); - fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */); + fling(velocity > 1f ? 1000f * velocity : 0 /* expand */); } onTrackingStopped(false); } @@ -2614,9 +2487,9 @@ public final class NotificationPanelViewController { break; case MotionEvent.ACTION_MOVE: - if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move"); + debugLog("onQSTouch move"); mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion"); - setQsExpansion(h + mInitialHeightOnTouch); + setQsExpansionHeight(h + mInitialHeightOnTouch); if (h >= getFalsingThreshold()) { mQsTouchAboveFalsingThreshold = true; } @@ -2663,7 +2536,7 @@ public final class NotificationPanelViewController { // Reset scroll position and apply that position to the expanded height. float height = mQsExpansionHeight; - setQsExpansion(height); + setQsExpansionHeight(height); updateExpandedHeightToMaxHeight(); mNotificationStackScrollLayoutController.checkSnoozeLeavebehind(); @@ -2691,6 +2564,9 @@ public final class NotificationPanelViewController { navigationBarView.onStatusBarPanelStateChanged(); } mShadeExpansionStateManager.onQsExpansionChanged(expanded); + mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded, + mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling, + mDozing, mQsAnimatorExpand, mAnimatingQS); } } @@ -2735,7 +2611,7 @@ public final class NotificationPanelViewController { mQs.setExpanded(mQsExpanded); } - void setQsExpansion(float height) { + void setQsExpansionHeight(float height) { height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0; boolean qsAnimatingAway = !mQsAnimatorExpand && mAnimatingQS; @@ -2902,7 +2778,7 @@ public final class NotificationPanelViewController { } private int calculateLeftQsClippingBound() { - if (isFullWidth()) { + if (mIsFullWidth) { // left bounds can ignore insets, it should always reach the edge of the screen return 0; } else { @@ -2911,7 +2787,7 @@ public final class NotificationPanelViewController { } private int calculateRightQsClippingBound() { - if (isFullWidth()) { + if (mIsFullWidth) { return getView().getRight() + mDisplayRightInset; } else { return mNotificationStackScrollLayoutController.getRight(); @@ -2979,7 +2855,7 @@ public final class NotificationPanelViewController { // Fancy clipping for quick settings int radius = mScrimCornerRadius; boolean clipStatusView = false; - if (isFullWidth()) { + if (mIsFullWidth) { // The padding on this area is large enough that we can use a cheaper clipping strategy mKeyguardStatusAreaClipBounds.set(left, top, right, bottom); clipStatusView = qsVisible; @@ -3042,11 +2918,23 @@ public final class NotificationPanelViewController { // relative to NotificationStackScrollLayout int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft(); int nsslRight = right - mNotificationStackScrollLayoutController.getLeft(); - int nsslTop = top - mNotificationStackScrollLayoutController.getTop(); + int nsslTop = getNotificationsClippingTopBounds(top); int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop(); int bottomRadius = mSplitShadeEnabled ? radius : 0; + int topRadius = mSplitShadeEnabled && mExpandingFromHeadsUp ? 0 : radius; mNotificationStackScrollLayoutController.setRoundedClippingBounds( - nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius); + nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius); + } + + private int getNotificationsClippingTopBounds(int qsTop) { + if (mSplitShadeEnabled && mExpandingFromHeadsUp) { + // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need + // to set top clipping bound to negative value to allow HUN to go up to the top edge of + // the screen without clipping. + return -mAmbientState.getStackTopMargin(); + } else { + return qsTop - mNotificationStackScrollLayoutController.getTop(); + } } private float getQSEdgePosition() { @@ -3117,10 +3005,7 @@ public final class NotificationPanelViewController { } } - /** - * @return the topPadding of notifications when on keyguard not respecting quick settings - * expansion - */ + /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */ private int getKeyguardNotificationStaticPadding() { if (!mKeyguardShowing) { return 0; @@ -3152,17 +3037,18 @@ public final class NotificationPanelViewController { * shade. 0.0f means we're not transitioning yet. */ public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) { - if (animate && isFullWidth()) { + if (animate && mIsFullWidth) { animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, delay); mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f; } - - if (mSplitShadeEnabled) { - updateQsExpansionForLockscreenToShadeTransition(pxAmount); - } float endPosition = 0; if (pxAmount > 0.0f) { + if (mSplitShadeEnabled) { + float qsHeight = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight, + mLockscreenShadeTransitionController.getQSDragProgress()); + setQsExpansionHeight(qsHeight); + } if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0 && !mMediaDataManager.hasActiveMediaOrRecommendation()) { // No notifications are visible, let's animate to the height of qs instead @@ -3200,22 +3086,7 @@ public final class NotificationPanelViewController { updateQsExpansion(); } - private void updateQsExpansionForLockscreenToShadeTransition(float pxAmount) { - float qsExpansion = 0; - if (pxAmount > 0.0f) { - qsExpansion = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight, - mLockscreenShadeTransitionController.getQSDragProgress()); - } - // SHADE_LOCKED means transition is over and we don't want further updates - if (mBarState != SHADE_LOCKED) { - setQsExpansion(qsExpansion); - } - } - - /** - * Notify the panel that the pulse expansion has finished and that we're going to the full - * shade - */ + /** Called when pulse expansion has finished and this is going to the full shade. */ public void onPulseExpansionFinished() { animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0); mIsPulseExpansionResetAnimator = true; @@ -3270,9 +3141,7 @@ public final class NotificationPanelViewController { } } - /** - * @see #flingSettings(float, int, Runnable, boolean) - */ + /** @see #flingSettings(float, int, Runnable, boolean) */ public void flingSettings(float vel, int type) { flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */); } @@ -3329,7 +3198,7 @@ public final class NotificationPanelViewController { animator.setDuration(350); } animator.addUpdateListener( - animation -> setQsExpansion((Float) animation.getAnimatedValue())); + animation -> setQsExpansionHeight((Float) animation.getAnimatedValue())); animator.addListener(new AnimatorListenerAdapter() { private boolean mIsCanceled; @@ -3405,7 +3274,8 @@ public final class NotificationPanelViewController { return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS); } - public int getMaxPanelHeight() { + @VisibleForTesting + int getMaxPanelHeight() { int min = mStatusBarMinHeight; if (!(mBarState == KEYGUARD) && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) { @@ -3439,19 +3309,35 @@ public final class NotificationPanelViewController { } private void onHeightUpdated(float expandedHeight) { + if (expandedHeight <= 0) { + mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.", + mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + } else if (isFullyExpanded()) { + mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.", + mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); + } if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { // Updating the clock position will set the top padding which might // trigger a new panel height and re-position the clock. // This is a circular dependency and should be avoided, otherwise we'll have // a stack overflow. if (mStackScrollerMeasuringPass > 2) { - if (DEBUG_LOGCAT) Log.d(TAG, "Unstable notification panel height. Aborting."); + debugLog("Unstable notification panel height. Aborting."); } else { positionClockAndNotifications(); } } - if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null - && !mQsExpansionFromOverscroll)) { + // Below is true when QS are expanded and we swipe up from the same bottom of panel to + // close the whole shade with one motion. Also this will be always true when closing + // split shade as there QS are always expanded so every collapsing motion is motion from + // expanded QS to closed panel + boolean collapsingShadeFromExpandedQs = mQsExpanded && !mQsTracking + && 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()) { float qsExpansionFraction; if (mSplitShadeEnabled) { qsExpansionFraction = 1; @@ -3470,7 +3356,7 @@ public final class NotificationPanelViewController { } float targetHeight = mQsMinExpansionHeight + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight); - setQsExpansion(targetHeight); + setQsExpansionHeight(targetHeight); } updateExpandedHeight(expandedHeight); updateHeader(); @@ -3572,9 +3458,7 @@ public final class NotificationPanelViewController { return alpha; } - /** - * Hides the header when notifications are colliding with it. - */ + /** Hides the header when notifications are colliding with it. */ private void updateHeader() { if (mBarState == KEYGUARD) { mKeyguardStatusBarViewController.updateViewState(); @@ -3717,7 +3601,7 @@ public final class NotificationPanelViewController { if (mAnimateAfterExpanding) { notifyExpandingStarted(); beginJankMonitoring(); - fling(0, true /* expand */); + fling(0 /* expand */); } else { setExpandedFraction(1f); } @@ -3754,6 +3638,24 @@ public final class NotificationPanelViewController { } + private void falsingAdditionalTapRequired() { + if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { + mTapAgainViewController.show(); + } else { + mKeyguardIndicationController.showTransientIndication( + R.string.notification_tap_again); + } + + if (!mStatusBarStateController.isDozing()) { + mVibratorHelper.vibrate( + Process.myUid(), + mView.getContext().getPackageName(), + ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT, + "falsing-additional-tap-required", + TOUCH_VIBRATION_ATTRIBUTES); + } + } + private void onTrackingStarted() { mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen()); endClosing(); @@ -3788,7 +3690,7 @@ public final class NotificationPanelViewController { private void updateMaxHeadsUpTranslation() { mNotificationStackScrollLayoutController.setHeadsUpBoundaries( - getHeight(), mNavigationBarBottomHeight); + mView.getHeight(), mNavigationBarBottomHeight); } @VisibleForTesting @@ -3833,7 +3735,8 @@ public final class NotificationPanelViewController { || !isTracking()); } - public int getMaxPanelTransitionDistance() { + @VisibleForTesting + int getMaxPanelTransitionDistance() { // Traditionally the value is based on the number of notifications. On split-shade, we want // the required distance to be a specific and constant value, to make sure the expansion // motion has the expected speed. We also only want this on non-lockscreen for now. @@ -3889,10 +3792,9 @@ public final class NotificationPanelViewController { } @VisibleForTesting - void setIsClosing(boolean isClosing) { - boolean wasClosing = isClosing(); - mClosing = isClosing; - if (wasClosing != isClosing) { + void setClosing(boolean isClosing) { + if (mClosing != isClosing) { + mClosing = isClosing; mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing); } mAmbientState.setIsClosing(isClosing); @@ -3905,10 +3807,6 @@ public final class NotificationPanelViewController { } } - public boolean isDozing() { - return mDozing; - } - public void setQsScrimEnabled(boolean qsScrimEnabled) { boolean changed = mQsScrimEnabled != qsScrimEnabled; mQsScrimEnabled = qsScrimEnabled; @@ -3921,7 +3819,7 @@ public final class NotificationPanelViewController { mKeyguardStatusViewController.dozeTimeTick(); } - private boolean onMiddleClicked() { + private void onMiddleClicked() { switch (mBarState) { case KEYGUARD: if (!mDozingOnDown) { @@ -3943,14 +3841,12 @@ public final class NotificationPanelViewController { startUnlockHintAnimation(); } } - return true; + break; case StatusBarState.SHADE_LOCKED: if (!mQsExpanded) { mStatusBarStateController.setState(KEYGUARD); } - return true; - default: - return true; + break; } } @@ -3986,6 +3882,7 @@ public final class NotificationPanelViewController { public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; + mHeadsUpManager.addListener(mOnHeadsUpChangedListener); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScrollLayoutController.getHeadsUpCallback(), NotificationPanelViewController.this); @@ -4024,17 +3921,9 @@ public final class NotificationPanelViewController { updateStatusBarIcons(); } - /** - * @return whether the notifications are displayed full width and don't have any margins on - * the side. - */ - public boolean isFullWidth() { - return mIsFullWidth; - } - private void updateStatusBarIcons() { boolean showIconsWhenExpanded = - (isPanelVisibleBecauseOfHeadsUp() || isFullWidth()) + (isPanelVisibleBecauseOfHeadsUp() || mIsFullWidth) && getExpandedHeight() < getOpeningHeight(); if (showIconsWhenExpanded && isOnKeyguard()) { showIconsWhenExpanded = false; @@ -4049,10 +3938,7 @@ public final class NotificationPanelViewController { return mBarState == KEYGUARD; } - /** - * Called when heads-up notification is being dragged up or down to indicate what's the starting - * height for shade motion - */ + /** Called when a HUN is dragged up or down to indicate the starting height for shade motion. */ public void setHeadsUpDraggingStartingHeight(int startHeight) { mHeadsUpStartHeight = startHeight; float scrimMinFraction; @@ -4106,25 +3992,18 @@ public final class NotificationPanelViewController { setLaunchingAffordance(false); } - /** - * Set whether we are currently launching an affordance. This is currently only set when - * launched via a camera gesture. - */ + /** Set whether we are currently launching an affordance (i.e. camera gesture). */ private void setLaunchingAffordance(boolean launchingAffordance) { mLaunchingAffordance = launchingAffordance; mKeyguardBypassController.setLaunchingAffordance(launchingAffordance); } - /** - * Return true when a bottom affordance is launching an occluded activity with a splash screen. - */ + /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */ public boolean isLaunchingAffordanceWithPreview() { return mLaunchingAffordance; } - /** - * Whether the camera application can be launched for the camera launch gesture. - */ + /** Whether the camera application can be launched by the camera launch gesture. */ public boolean canCameraGestureBeLaunched() { return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState); } @@ -4137,22 +4016,19 @@ public final class NotificationPanelViewController { && mHeadsUpAppearanceController.shouldBeVisible()) { return false; } - return !isFullWidth() || !mShowIconsWhenExpanded; + return !mIsFullWidth || !mShowIconsWhenExpanded; } - public final QS.ScrollListener mScrollListener = new QS.ScrollListener() { - @Override - public void onQsPanelScrollChanged(int scrollY) { - mLargeScreenShadeHeaderController.setQsScrollY(scrollY); - if (scrollY > 0 && !mQsFullyExpanded) { - if (DEBUG_LOGCAT) Log.d(TAG, "Scrolling while not expanded. Forcing expand"); - // If we are scrolling QS, we should be fully expanded. - expandWithQs(); - } + private void onQsPanelScrollChanged(int scrollY) { + mLargeScreenShadeHeaderController.setQsScrollY(scrollY); + if (scrollY > 0 && !mQsFullyExpanded) { + debugLog("Scrolling while not expanded. Forcing expand"); + // If we are scrolling QS, we should be fully expanded. + expandWithQs(); } - }; + } - private final FragmentListener mFragmentListener = new FragmentListener() { + private final class QsFragmentListener implements FragmentListener { @Override public void onFragmentViewCreated(String tag, Fragment fragment) { mQs = (QS) fragment; @@ -4169,7 +4045,7 @@ public final class NotificationPanelViewController { final int height = bottom - top; final int oldHeight = oldBottom - oldTop; if (height != oldHeight) { - mHeightListener.onQsHeightChanged(); + onQsHeightChanged(); } }); mQs.setCollapsedMediaVisibilityChangedListener((visible) -> { @@ -4182,7 +4058,7 @@ public final class NotificationPanelViewController { mLockscreenShadeTransitionController.setQS(mQs); mShadeTransitionController.setQs(mQs); mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader()); - mQs.setScrollListener(mScrollListener); + mQs.setScrollListener(mQsScrollListener); updateQsExpansion(); } @@ -4195,7 +4071,7 @@ public final class NotificationPanelViewController { mQs = null; } } - }; + } private void animateNextNotificationBounds(long duration, long delay) { mAnimateNextNotificationBounds = true; @@ -4285,13 +4161,7 @@ public final class NotificationPanelViewController { mKeyguardStatusViewController.setStatusAccessibilityImportance(mode); } - /** - * TODO: this should be removed. - * It's not correct to pass this view forward because other classes will end up adding - * children to it. Theme will be out of sync. - * - * @return bottom area view - */ + //TODO(b/254875405): this should be removed. public KeyguardBottomAreaView getKeyguardBottomAreaView() { return mKeyguardBottomArea; } @@ -4320,11 +4190,8 @@ public final class NotificationPanelViewController { mHeadsUpAppearanceController = headsUpAppearanceController; } - /** - * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the - * security view of the bouncer. - */ - public void onBouncerPreHideAnimation() { + /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */ + public void startBouncerPreHideAnimation() { if (mKeyguardQsUserSwitchController != null) { mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( mBarState, @@ -4341,9 +4208,7 @@ public final class NotificationPanelViewController { } } - /** - * Updates the views to the initial state for the fold to AOD animation - */ + /** Updates the views to the initial state for the fold to AOD animation. */ public void prepareFoldToAodAnimation() { // Force show AOD UI even if we are not locked showAodUi(); @@ -4385,14 +4250,11 @@ public final class NotificationPanelViewController { public void onAnimationEnd(Animator animation) { endAction.run(); } - }).setUpdateListener(anim -> { - mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction()); - }).start(); + }).setUpdateListener(anim -> mKeyguardStatusViewController.animateFoldToAod( + anim.getAnimatedFraction())).start(); } - /** - * Cancels fold to AOD transition and resets view state - */ + /** Cancels fold to AOD transition and resets view state. */ public void cancelFoldToAodAnimation() { cancelAnimation(); resetAlpha(); @@ -4436,42 +4298,11 @@ public final class NotificationPanelViewController { } } - public boolean hasActiveClearableNotifications() { - return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL); - } public RemoteInputController.Delegate createRemoteInputDelegate() { return mNotificationStackScrollLayoutController.createDelegate(); } - /** - * Updates the notification views' sections and status bar icons. This is - * triggered by the NotificationPresenter whenever there are changes to the underlying - * notification data being displayed. In the new notification pipeline, this is handled in - * {@link ShadeViewManager}. - */ - public void updateNotificationViews() { - mNotificationStackScrollLayoutController.updateFooter(); - - mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList()); - } - - private List<ListEntry> createVisibleEntriesList() { - List<ListEntry> entries = new ArrayList<>( - mNotificationStackScrollLayoutController.getChildCount()); - for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) { - View view = mNotificationStackScrollLayoutController.getChildAt(i); - if (view instanceof ExpandableNotificationRow) { - entries.add(((ExpandableNotificationRow) view).getEntry()); - } - } - return entries; - } - - public void onUpdateRowStates() { - mNotificationStackScrollLayoutController.onUpdateRowStates(); - } - public boolean hasPulsingNotifications() { return mNotificationListContainer.hasPulsingNotifications(); } @@ -4488,16 +4319,6 @@ public final class NotificationPanelViewController { mNotificationStackScrollLayoutController.runAfterAnimationFinished(r); } - private Runnable mHideExpandedRunnable; - private final Runnable mMaybeHideExpandedRunnable = new Runnable() { - @Override - public void run() { - if (getExpansionFraction() == 0.0f) { - mView.post(mHideExpandedRunnable); - } - } - }; - /** * Initialize objects instead of injecting to avoid circular dependencies. * @@ -4507,7 +4328,9 @@ public final class NotificationPanelViewController { CentralSurfaces centralSurfaces, Runnable hideExpandedRunnable, NotificationShelfController notificationShelfController) { - setCentralSurfaces(centralSurfaces); + // TODO(b/254859580): this can be injected. + mCentralSurfaces = centralSurfaces; + mHideExpandedRunnable = hideExpandedRunnable; mNotificationStackScrollLayoutController.setShelfController(notificationShelfController); mNotificationShelfController = notificationShelfController; @@ -4515,10 +4338,6 @@ public final class NotificationPanelViewController { updateMaxDisplayedNotifications(true); } - public void setAlpha(float alpha) { - mView.setAlpha(alpha); - } - public void resetTranslation() { mView.setTranslationX(0f); } @@ -4537,22 +4356,14 @@ public final class NotificationPanelViewController { ViewGroupFadeHelper.reset(mView); } - public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().addOnGlobalLayoutListener(listener); } - public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } - public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() { - return mOnHeadsUpChangedListener; - } - - public int getHeight() { - return mView.getHeight(); - } - public void setHeaderDebugInfo(String text) { if (DEBUG_DRAWABLE) mHeaderDebugInfo = text; } @@ -4561,10 +4372,6 @@ public final class NotificationPanelViewController { mConfigurationListener.onThemeChanged(); } - private OnLayoutChangeListener createLayoutChangeListener() { - return new OnLayoutChangeListener(); - } - @VisibleForTesting TouchHandler createTouchHandler() { return new TouchHandler(); @@ -4619,10 +4426,6 @@ public final class NotificationPanelViewController { } }; - private OnConfigurationChangedListener createOnConfigurationChangedListener() { - return new OnConfigurationChangedListener(); - } - public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() { return mNotificationStackScrollLayoutController; } @@ -4663,13 +4466,7 @@ public final class NotificationPanelViewController { ); } - private void unregisterSettingsChangeListener() { - mContentResolver.unregisterContentObserver(mSettingsChangeObserver); - } - - /** - * Updates notification panel-specific flags on {@link SysUiState}. - */ + /** Updates notification panel-specific flags on {@link SysUiState}. */ public void updateSystemUiStateFlags() { if (SysUiState.DEBUG) { Log.d(TAG, "Updating panel sysui state flags: fullyExpanded=" @@ -4681,8 +4478,10 @@ public final class NotificationPanelViewController { .commitUpdate(mDisplayId); } - private void logf(String fmt, Object... args) { - Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); + private void debugLog(String fmt, Object... args) { + if (DEBUG_LOGCAT) { + Log.d(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args)); + } } @VisibleForTesting @@ -4729,8 +4528,6 @@ public final class NotificationPanelViewController { private void startOpening(MotionEvent event) { updatePanelExpansionAndVisibility(); - // Reset at start so haptic can be triggered as soon as panel starts to open. - mHasVibratedOnOpen = false; //TODO: keyguard opens QS a different way; log that too? // Log the position of the swipe that opened the panel @@ -4748,9 +4545,8 @@ public final class NotificationPanelViewController { * Maybe vibrate as panel is opened. * * @param openingWithTouch Whether the panel is being opened with touch. If the panel is - * instead - * being opened programmatically (such as by the open panel gesture), we - * always play haptic. + * instead being opened programmatically (such as by the open panel + * gesture), we always play haptic. */ private void maybeVibrateOnOpening(boolean openingWithTouch) { if (mVibrateOnOpening) { @@ -4847,10 +4643,10 @@ public final class NotificationPanelViewController { mUpdateFlingVelocity = vel; } } else if (!mCentralSurfaces.isBouncerShowing() - && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating() + && !mStatusBarKeyguardViewManager.isShowingAlternateAuth() && !mKeyguardStateController.isKeyguardGoingAway()) { - boolean expands = onEmptySpaceClick(); - onTrackingStopped(expands); + onEmptySpaceClick(); + onTrackingStopped(true); } mVelocityTracker.clear(); } @@ -4862,7 +4658,7 @@ public final class NotificationPanelViewController { private void endClosing() { if (mClosing) { - setIsClosing(false); + setClosing(false); onClosingFinished(); } } @@ -4897,7 +4693,7 @@ public final class NotificationPanelViewController { boolean expandBecauseOfFalsing) { float target = expand ? getMaxPanelHeight() : 0; if (!expand) { - setIsClosing(true); + setClosing(true); } flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); } @@ -4932,13 +4728,9 @@ public final class NotificationPanelViewController { animator.start(); } - public String getName() { - return mViewName; - } - @VisibleForTesting void setExpandedHeight(float height) { - if (DEBUG) logf("setExpandedHeight(%.1f)", height); + debugLog("setExpandedHeight(%.1f)", height); setExpandedHeightInternal(height); } @@ -5030,12 +4822,12 @@ public final class NotificationPanelViewController { return mExpandedHeight; } - public float getExpandedFraction() { + private float getExpandedFraction() { return mExpandedFraction; } public boolean isFullyExpanded() { - return mExpandedHeight >= getMaxPanelHeight(); + return mExpandedHeight >= getMaxPanelTransitionDistance(); } public boolean isFullyCollapsed() { @@ -5046,10 +4838,6 @@ public final class NotificationPanelViewController { return mClosing || mIsLaunchAnimationRunning; } - public boolean isFlinging() { - return mIsFlinging; - } - public boolean isTracking() { return mTracking; } @@ -5196,8 +4984,7 @@ public final class NotificationPanelViewController { */ public void updatePanelExpansionAndVisibility() { mShadeExpansionStateManager.onPanelExpansionChanged( - mExpandedFraction, isExpanded(), - mTracking, mExpansionDragDownAmountPx); + mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx); updateVisibility(); } @@ -5210,16 +4997,11 @@ public final class NotificationPanelViewController { && !mIsSpringBackAnimation; } - /** - * Gets called when the user performs a click anywhere in the empty area of the panel. - * - * @return whether the panel will be expanded after the action performed by this method - */ - private boolean onEmptySpaceClick() { - if (mHintAnimationRunning) { - return true; + /** Called when the user performs a click anywhere in the empty area of the panel. */ + private void onEmptySpaceClick() { + if (!mHintAnimationRunning) { + onMiddleClicked(); } - return onMiddleClicked(); } @VisibleForTesting @@ -5236,7 +5018,7 @@ public final class NotificationPanelViewController { /** Returns the NotificationPanelView. */ public ViewGroup getView() { - // TODO: remove this method, or at least reduce references to it. + // TODO(b/254878364): remove this method, or at least reduce references to it. return mView; } @@ -5276,12 +5058,11 @@ public final class NotificationPanelViewController { return mShadeExpansionStateManager; } - private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener { + private final class NsslHeightChangedListener implements + ExpandableView.OnHeightChangedListener { @Override public void onHeightChanged(ExpandableView view, boolean needsAnimation) { - - // Block update if we are in quick settings and just the top padding changed - // (i.e. view == null). + // Block update if we are in QS and just the top padding changed (i.e. view == null). if (view == null && mQsExpanded) { return; } @@ -5305,26 +5086,22 @@ public final class NotificationPanelViewController { } @Override - public void onReset(ExpandableView view) { - } + public void onReset(ExpandableView view) {} } - private class CollapseExpandAction implements Runnable { - @Override - public void run() { - onQsExpansionStarted(); - if (mQsExpanded) { - flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */, - true /* isClick */); - } else if (isQsExpansionEnabled()) { - mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0); - flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */, - true /* isClick */); - } + private void collapseOrExpand() { + onQsExpansionStarted(); + if (mQsExpanded) { + flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */, + true /* isClick */); + } else if (isQsExpansionEnabled()) { + mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0); + flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */, + true /* isClick */); } } - private class OnOverscrollTopChangedListener implements + private final class NsslOverscrollTopChangedListener implements NotificationStackScrollLayout.OnOverscrollTopChangedListener { @Override public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { @@ -5341,7 +5118,7 @@ public final class NotificationPanelViewController { mQsExpansionFromOverscroll = rounded != 0f; mLastOverscroll = rounded; updateQsState(); - setQsExpansion(mQsMinExpansionHeight + rounded); + setQsExpansionHeight(mQsMinExpansionHeight + rounded); } @Override @@ -5358,7 +5135,7 @@ public final class NotificationPanelViewController { // make sure we can expand setOverScrolling(false); } - setQsExpansion(mQsExpansionHeight); + setQsExpansionHeight(mQsExpansionHeight); boolean canExpand = isQsExpansionEnabled(); flingSettings(!canExpand && open ? 0f : velocity, open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> { @@ -5368,27 +5145,16 @@ public final class NotificationPanelViewController { } } - private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener { - @Override - public void onDynamicPrivacyChanged() { - // Do not request animation when pulsing or waking up, otherwise the clock wiill be out - // of sync with the notification panel. - if (mLinearDarkAmount != 0) { - return; - } - mAnimateNextPositionUpdate = true; - } - } - - private class OnEmptySpaceClickListener implements - NotificationStackScrollLayout.OnEmptySpaceClickListener { - @Override - public void onEmptySpaceClicked(float x, float y) { - onEmptySpaceClick(); + private void onDynamicPrivacyChanged() { + // Do not request animation when pulsing or waking up, otherwise the clock will be out + // of sync with the notification panel. + if (mLinearDarkAmount != 0) { + return; } + mAnimateNextPositionUpdate = true; } - private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener { + private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener { @Override public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) { if (inPinnedMode) { @@ -5428,32 +5194,31 @@ public final class NotificationPanelViewController { } } - private class HeightListener implements QS.HeightListener { - public void onQsHeightChanged() { - mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0; - if (mQsExpanded && mQsFullyExpanded) { - mQsExpansionHeight = mQsMaxExpansionHeight; - requestScrollerTopPaddingUpdate(false /* animate */); - updateExpandedHeightToMaxHeight(); - } - if (mAccessibilityManager.isEnabled()) { - mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); - } - mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight); + private void onQsHeightChanged() { + mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0; + if (mQsExpanded && mQsFullyExpanded) { + mQsExpansionHeight = mQsMaxExpansionHeight; + requestScrollerTopPaddingUpdate(false /* animate */); + updateExpandedHeightToMaxHeight(); + } + if (mAccessibilityManager.isEnabled()) { + mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle()); } + mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight); } - private class ConfigurationListener implements ConfigurationController.ConfigurationListener { + private final class ConfigurationListener implements + ConfigurationController.ConfigurationListener { @Override public void onThemeChanged() { - if (DEBUG_LOGCAT) Log.d(TAG, "onThemeChanged"); + debugLog("onThemeChanged"); reInflateViews(); } @Override public void onSmallestScreenWidthChanged() { Trace.beginSection("onSmallestScreenWidthChanged"); - if (DEBUG_LOGCAT) Log.d(TAG, "onSmallestScreenWidthChanged"); + debugLog("onSmallestScreenWidthChanged"); // Can affect multi-user switcher visibility as it depends on screen size by default: // it is enabled only for devices with large screens (see config_keyguardUserSwitcher) @@ -5470,27 +5235,26 @@ public final class NotificationPanelViewController { @Override public void onDensityOrFontScaleChanged() { - if (DEBUG_LOGCAT) Log.d(TAG, "onDensityOrFontScaleChanged"); + debugLog("onDensityOrFontScaleChanged"); reInflateViews(); } } - private class SettingsChangeObserver extends ContentObserver { - + private final class SettingsChangeObserver extends ContentObserver { SettingsChangeObserver(Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { - if (DEBUG_LOGCAT) Log.d(TAG, "onSettingsChanged"); + debugLog("onSettingsChanged"); // Can affect multi-user switcher visibility reInflateViews(); } } - private class StatusBarStateListener implements StateListener { + private final class StatusBarStateListener implements StateListener { @Override public void onStateChanged(int statusBarState) { boolean goingToFullShade = mStatusBarStateController.goingToFullShade(); @@ -5551,7 +5315,7 @@ public final class NotificationPanelViewController { } } else { // this else branch means we are doing one of: - // - from KEYGUARD and SHADE (but not expanded shade) + // - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top) // - from SHADE to KEYGUARD // - from SHADE_LOCKED to SHADE // - getting notified again about the current SHADE or KEYGUARD state @@ -5646,21 +5410,19 @@ public final class NotificationPanelViewController { setExpandedFraction(1f); } - /** - * Sets the overstretch amount in raw pixels when dragging down. - */ - public void setOverStrechAmount(float amount) { + /** Sets the overstretch amount in raw pixels when dragging down. */ + public void setOverStretchAmount(float amount) { float progress = amount / mView.getHeight(); - float overstretch = Interpolators.getOvershootInterpolation(progress); - mOverStretchAmount = overstretch * mMaxOverscrollAmountForPulse; + float overStretch = Interpolators.getOvershootInterpolation(progress); + mOverStretchAmount = overStretch * mMaxOverscrollAmountForPulse; positionClockAndNotifications(true /* forceUpdate */); } - private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener { + private final class ShadeAttachStateChangeListener implements View.OnAttachStateChangeListener { @Override public void onViewAttachedToWindow(View v) { mFragmentService.getFragmentHostManager(mView) - .addTagListener(QS.TAG, mFragmentListener); + .addTagListener(QS.TAG, mQsFragmentListener); mStatusBarStateController.addCallback(mStatusBarStateListener); mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState()); mConfigurationController.addCallback(mConfigurationListener); @@ -5675,16 +5437,16 @@ public final class NotificationPanelViewController { @Override public void onViewDetachedFromWindow(View v) { - unregisterSettingsChangeListener(); + mContentResolver.unregisterContentObserver(mSettingsChangeObserver); mFragmentService.getFragmentHostManager(mView) - .removeTagListener(QS.TAG, mFragmentListener); + .removeTagListener(QS.TAG, mQsFragmentListener); mStatusBarStateController.removeCallback(mStatusBarStateListener); mConfigurationController.removeCallback(mConfigurationListener); mFalsingManager.removeTapListener(mFalsingTapListener); } } - private final class OnLayoutChangeListener implements View.OnLayoutChangeListener { + private final class ShadeLayoutChangeListener implements View.OnLayoutChangeListener { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { @@ -5693,7 +5455,7 @@ public final class NotificationPanelViewController { mHasLayoutedSinceDown = true; if (mUpdateFlingOnLayout) { abortAnimations(); - fling(mUpdateFlingVelocity, true /* expands */); + fling(mUpdateFlingVelocity); mUpdateFlingOnLayout = false; } updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount()); @@ -5720,21 +5482,18 @@ public final class NotificationPanelViewController { startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); } } else if (!mQsExpanded && mQsExpansionAnimator == null) { - setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); + setQsExpansionHeight(mQsMinExpansionHeight + mLastOverscroll); } else { mShadeLog.v("onLayoutChange: qs expansion not set"); } updateExpandedHeight(getExpandedHeight()); updateHeader(); - // If we are running a size change animation, the animation takes care of the height of - // the container. However, if we are not animating, we always need to make the QS - // container - // the desired height so when closing the QS detail, it stays smaller after the size - // change - // animation is finished but the detail view is still being animated away (this - // animation - // takes longer than the size change animation). + // If we are running a size change animation, the animation takes care of the height + // of the container. However, if we are not animating, we always need to make the QS + // container the desired height so when closing the QS detail, it stays smaller after + // the size change animation is finished but the detail view is still being animated + // away (this animation takes longer than the size change animation). if (mQsSizeChangeAnimator == null && mQs != null) { mQs.setHeightOverride(mQs.getDesiredHeight()); } @@ -5760,13 +5519,12 @@ public final class NotificationPanelViewController { } } - private class DebugDrawable extends Drawable { - + private final class DebugDrawable extends Drawable { private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>(); private final Paint mDebugPaint = new Paint(); @Override - public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) { + public void draw(@NonNull Canvas canvas) { mDebugTextUsedYPositions.clear(); mDebugPaint.setColor(Color.RED); @@ -5844,18 +5602,17 @@ public final class NotificationPanelViewController { } } - private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener { - public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { - // the same types of insets that are handled in NotificationShadeWindowView - int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout(); - Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes); - mDisplayTopInset = combinedInsets.top; - mDisplayRightInset = combinedInsets.right; + @NonNull + private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) { + // the same types of insets that are handled in NotificationShadeWindowView + int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout(); + Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes); + mDisplayTopInset = combinedInsets.top; + mDisplayRightInset = combinedInsets.right; - mNavigationBarBottomHeight = insets.getStableInsetBottom(); - updateMaxHeadsUpTranslation(); - return insets; - } + mNavigationBarBottomHeight = insets.getStableInsetBottom(); + updateMaxHeadsUpTranslation(); + return insets; } /** Removes any pending runnables that would collapse the panel. */ @@ -5863,9 +5620,6 @@ public final class NotificationPanelViewController { mView.removeCallbacks(mMaybeHideExpandedRunnable); } - @PanelState - private int mCurrentPanelState = STATE_CLOSED; - private void onPanelStateChanged(@PanelState int state) { updateQSExpansionEnabledAmbient(); @@ -5902,6 +5656,11 @@ public final class NotificationPanelViewController { } @VisibleForTesting + StateListener getStatusBarStateListener() { + return mStatusBarStateListener; + } + + @VisibleForTesting boolean isHintAnimationRunning() { return mHintAnimationRunning; } @@ -5922,6 +5681,7 @@ public final class NotificationPanelViewController { /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */ public boolean onInterceptTouchEvent(MotionEvent event) { + mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent"); if (SPEW_LOGCAT) { Log.v(TAG, "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX() @@ -5934,6 +5694,8 @@ public final class NotificationPanelViewController { // Do not let touches go to shade or QS if the bouncer is visible, // but still let user swipe down to expand the panel, dismissing the bouncer. if (mCentralSurfaces.isBouncerShowing()) { + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "bouncer is showing"); return true; } if (mCommandQueue.panelsEnabled() @@ -5941,15 +5703,21 @@ public final class NotificationPanelViewController { && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { mMetricsLogger.count(COUNTER_PANEL_OPEN, 1); mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1); + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "HeadsUpTouchHelper"); return true; } if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0) && mPulseExpansionHandler.onInterceptTouchEvent(event)) { + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "PulseExpansionHandler"); return true; } if (!isFullyCollapsed() && onQsIntercept(event)) { - if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true"); + debugLog("onQsIntercept true"); + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: " + + "QsIntercept"); return true; } if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted @@ -5980,6 +5748,9 @@ public final class NotificationPanelViewController { if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) { cancelHeightAnimator(); mTouchSlopExceeded = true; + mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:" + + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:" + + " false"); return true; } mInitialExpandY = y; @@ -6024,6 +5795,8 @@ public final class NotificationPanelViewController { && hAbs > Math.abs(x - mInitialExpandX)) { cancelHeightAnimator(); startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); + mShadeLog.v("NotificationPanelViewController MotionEvent" + + " intercepted: startExpandMotion"); return true; } } @@ -6152,7 +5925,6 @@ public final class NotificationPanelViewController { * * Flinging is also enabled in order to open or close the shade. */ - int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -6168,6 +5940,7 @@ public final class NotificationPanelViewController { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: + mShadeLog.logMotionEvent(event, "onTouch: down action"); startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mMinExpandHeight = 0.0f; mPanelClosedOnDown = isFullyCollapsed(); @@ -6214,6 +5987,10 @@ public final class NotificationPanelViewController { } break; case MotionEvent.ACTION_MOVE: + if (isFullyCollapsed()) { + // If panel is fully collapsed, reset haptic effect before adding movement. + mHasVibratedOnOpen = false; + } addMovement(event); if (!isFullyCollapsed()) { maybeVibrateOnOpening(true /* openingWithTouch */); @@ -6252,6 +6029,7 @@ public final class NotificationPanelViewController { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: + mShadeLog.logMotionEvent(event, "onTouch: up/cancel action"); addMovement(event); endMotionEvent(event, x, y, false /* forceCancel */); // mHeightAnimator is null, there is no remaining frame, ends instrumenting. @@ -6268,15 +6046,6 @@ public final class NotificationPanelViewController { } } - /** Listens for config changes. */ - public class OnConfigurationChangedListener implements - NotificationPanelView.OnConfigurationChangedListener { - @Override - public void onConfigurationChanged(Configuration newConfig) { - loadDimens(); - } - } - static class SplitShadeTransitionAdapter extends Transition { private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds"; private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS }; @@ -6326,5 +6095,27 @@ public final class NotificationPanelViewController { return TRANSITION_PROPERTIES; } } + + private final class ShadeAccessibilityDelegate extends AccessibilityDelegate { + @Override + public void onInitializeAccessibilityNodeInfo(View host, + AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (action + == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId() + || action + == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) { + mStatusBarKeyguardViewManager.showBouncer(true); + return true; + } + return super.performAccessibilityAction(host, action, args); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 65bd58d0d801..1e63b2dd134f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -284,7 +284,7 @@ public class NotificationShadeWindowViewController { return true; } - if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { + if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { // capture all touches if the alt auth bouncer is showing return true; } @@ -322,7 +322,7 @@ public class NotificationShadeWindowViewController { handled = !mService.isPulsing(); } - if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) { + if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { // eat the touch handled = true; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index 084b7dc3a646..bf622c941abb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -52,6 +52,7 @@ class PulsingGestureListener @Inject constructor( private val centralSurfaces: CentralSurfaces, private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val statusBarStateController: StatusBarStateController, + private val shadeLogger: ShadeLogger, tunerService: TunerService, dumpManager: DumpManager ) : GestureDetector.SimpleOnGestureListener(), Dumpable { @@ -77,18 +78,23 @@ class PulsingGestureListener @Inject constructor( } override fun onSingleTapUp(e: MotionEvent): Boolean { - if (statusBarStateController.isDozing && - singleTapEnabled && - !dockManager.isDocked && - !falsingManager.isProximityNear && - !falsingManager.isFalseTap(LOW_PENALTY) - ) { - centralSurfaces.wakeUpIfDozing( + val isNotDocked = !dockManager.isDocked + shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked) + if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) { + val proximityIsNotNear = !falsingManager.isProximityNear + val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY) + shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap) + if (proximityIsNotNear && isNotAFalseTap) { + shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing") + centralSurfaces.wakeUpIfDozing( SystemClock.uptimeMillis(), notificationShadeWindowView, - "PULSING_SINGLE_TAP") + "PULSING_SINGLE_TAP" + ) + } return true } + shadeLogger.d("onSingleTapUp event ignored") return false } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index f389dd970fbc..eaf7faecd5b3 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -224,6 +224,6 @@ public class ShadeControllerImpl implements ShadeController { } private NotificationPanelViewController getNotificationPanelViewController() { - return getCentralSurfaces().getPanelController(); + return getCentralSurfaces().getNotificationPanelViewController(); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 2b788d85a14c..761530101061 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -16,6 +16,10 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { buffer.log(TAG, LogLevel.VERBOSE, msg) } + fun d(@CompileTimeConstant msg: String) { + buffer.log(TAG, LogLevel.DEBUG, msg) + } + private inline fun log( logLevel: LogLevel, initializer: LogMessage.() -> Unit, @@ -77,4 +81,71 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { } ) } + + fun logExpansionChanged( + message: String, + fraction: Float, + expanded: Boolean, + tracking: Boolean, + dragDownPxAmount: Float, + ) { + log(LogLevel.VERBOSE, { + str1 = message + double1 = fraction.toDouble() + bool1 = expanded + bool2 = tracking + long1 = dragDownPxAmount.toLong() + }, { + "$str1 fraction=$double1,expanded=$bool1," + + "tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount" + }) + } + + fun logQsExpansionChanged( + message: String, + qsExpanded: Boolean, + qsMinExpansionHeight: Int, + qsMaxExpansionHeight: Int, + stackScrollerOverscrolling: Boolean, + dozing: Boolean, + qsAnimatorExpand: Boolean, + animatingQs: Boolean + ) { + log(LogLevel.VERBOSE, { + str1 = message + bool1 = qsExpanded + int1 = qsMinExpansionHeight + int2 = qsMaxExpansionHeight + bool2 = stackScrollerOverscrolling + bool3 = dozing + bool4 = qsAnimatorExpand + // 0 = false, 1 = true + long1 = animatingQs.compareTo(false).toLong() + }, { + "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," + + "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," + + "animatingQs=$long1" + }) + } + + fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) { + log(LogLevel.DEBUG, { + bool1 = isDozing + bool2 = singleTapEnabled + bool3 = isNotDocked + }, { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + + "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3" + }) + } + + fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) { + log(LogLevel.DEBUG, { + bool1 = proximityIsNotNear + bool2 = isNotFalseTap + }, { + "PulsingGestureListener#onSingleTapUp all of this must true for single " + + "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2" + }) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 4ae0f6a4a6c8..b821a2f374fe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -39,7 +39,7 @@ import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; @@ -154,7 +154,7 @@ public class CommandQueue extends IStatusBar.Stub implements //TODO(b/169175022) Update name and when feature name is locked. private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE = 58 << MSG_SHIFT; private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT; - private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT; + private static final int MSG_SET_UDFPS_REFRESH_RATE_CALLBACK = 60 << MSG_SHIFT; private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT; private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT; private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT; @@ -333,9 +333,9 @@ public class CommandQueue extends IStatusBar.Stub implements } /** - * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener) + * @see IStatusBar#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback) */ - default void setUdfpsHbmListener(IUdfpsHbmListener listener) { + default void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { } /** @@ -1017,9 +1017,9 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void setUdfpsHbmListener(IUdfpsHbmListener listener) { + public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { synchronized (mLock) { - mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget(); + mHandler.obtainMessage(MSG_SET_UDFPS_REFRESH_RATE_CALLBACK, callback).sendToTarget(); } } @@ -1546,9 +1546,10 @@ public class CommandQueue extends IStatusBar.Stub implements (IBiometricContextListener) msg.obj); } break; - case MSG_SET_UDFPS_HBM_LISTENER: + case MSG_SET_UDFPS_REFRESH_RATE_CALLBACK: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj); + mCallbacks.get(i).setUdfpsRefreshRateCallback( + (IUdfpsRefreshRateRequestCallback) msg.obj); } break; case MSG_SHOW_CHARGING_ANIMATION: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index a2e4536ce45f..b8302d706e8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -663,7 +663,7 @@ class LockscreenShadeTransitionController @Inject constructor( } else { pulseHeight = height val overflow = nsslController.setPulseHeight(height) - notificationPanelController.setOverStrechAmount(overflow) + notificationPanelController.setOverStretchAmount(overflow) val transitionHeight = if (keyguardBypassController.bypassEnabled) height else 0.0f transitionToShadeAmountCommon(transitionHeight) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 87ef92a28d5d..408293cffd99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -496,9 +496,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return; } - final float smallCornerRadius = - getResources().getDimension(R.dimen.notification_corner_radius_small) - / getResources().getDimension(R.dimen.notification_corner_radius); final float viewEnd = viewStart + anv.getActualHeight(); final float cornerAnimationDistance = mCornerAnimationDistance * mAmbientState.getExpansionFraction(); @@ -509,7 +506,7 @@ public class NotificationShelf extends ActivatableNotificationView implements final float changeFraction = MathUtils.saturate( (viewEnd - cornerAnimationTop) / cornerAnimationDistance); anv.requestBottomRoundness( - anv.isLastInSection() ? 1f : changeFraction, + /* value = */ anv.isLastInSection() ? 1f : changeFraction, /* animate = */ false, SourceType.OnScroll); @@ -517,7 +514,7 @@ public class NotificationShelf extends ActivatableNotificationView implements // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. anv.requestBottomRoundness( - anv.isLastInSection() ? 1f : smallCornerRadius, + /* value = */ anv.isLastInSection() ? 1f : 0f, /* animate = */ false, SourceType.OnScroll); } @@ -527,16 +524,16 @@ public class NotificationShelf extends ActivatableNotificationView implements final float changeFraction = MathUtils.saturate( (viewStart - cornerAnimationTop) / cornerAnimationDistance); anv.requestTopRoundness( - anv.isFirstInSection() ? 1f : changeFraction, - false, + /* value = */ anv.isFirstInSection() ? 1f : changeFraction, + /* animate = */ false, SourceType.OnScroll); } else if (viewStart < cornerAnimationTop) { // Fast scroll skips frames and leaves corners with unfinished rounding. // Reset top and bottom corners outside of animation bounds. anv.requestTopRoundness( - anv.isFirstInSection() ? 1f : smallCornerRadius, - false, + /* value = */ anv.isFirstInSection() ? 1f : 0f, + /* animate = */ false, SourceType.OnScroll); } } @@ -976,6 +973,16 @@ public class NotificationShelf extends ActivatableNotificationView implements mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); } + /** + * This method resets the OnScroll roundness of a view to 0f + * + * 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 class ShelfState extends ExpandableViewState { private boolean hasItemsInStableShelf; private ExpandableView firstViewInShelf; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index fc984618f1b0..58738377a3db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -48,7 +48,8 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.regionsampling.RegionSamplingInstance +import com.android.systemui.shared.regionsampling.RegionSampler +import com.android.systemui.shared.regionsampling.UpdateColorCallback import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController @@ -90,8 +91,8 @@ class LockscreenSmartspaceController @Inject constructor( // Smartspace can be used on multiple displays, such as when the user casts their screen private var smartspaceViews = mutableSetOf<SmartspaceView>() - private var regionSamplingInstances = - mutableMapOf<SmartspaceView, RegionSamplingInstance>() + private var regionSamplers = + mutableMapOf<SmartspaceView, RegionSampler>() private val regionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING) @@ -101,27 +102,23 @@ class LockscreenSmartspaceController @Inject constructor( private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null - private val updateFun = object : RegionSamplingInstance.UpdateColorCallback { - override fun updateColors() { - updateTextColorFromRegionSampler() - } - } + private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() } // TODO: Move logic into SmartspaceView var stateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { smartspaceViews.add(v as SmartspaceView) - var regionSamplingInstance = RegionSamplingInstance( + var regionSampler = RegionSampler( v, uiExecutor, bgExecutor, regionSamplingEnabled, updateFun ) - initializeTextColors(regionSamplingInstance) - regionSamplingInstance.startRegionSampler() - regionSamplingInstances.put(v, regionSamplingInstance) + initializeTextColors(regionSampler) + regionSampler.startRegionSampler() + regionSamplers.put(v, regionSampler) connectSession() updateTextColorFromWallpaper() @@ -131,9 +128,9 @@ class LockscreenSmartspaceController @Inject constructor( override fun onViewDetachedFromWindow(v: View) { smartspaceViews.remove(v as SmartspaceView) - var regionSamplingInstance = regionSamplingInstances.getValue(v) - regionSamplingInstance.stopRegionSampler() - regionSamplingInstances.remove(v) + var regionSampler = regionSamplers.getValue(v) + regionSampler.stopRegionSampler() + regionSamplers.remove(v) if (smartspaceViews.isEmpty()) { disconnect() @@ -363,19 +360,19 @@ class LockscreenSmartspaceController @Inject constructor( } } - private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) { + private fun initializeTextColors(regionSampler: RegionSampler) { val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper) val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor) val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI) val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor) - regionSamplingInstance.setForegroundColors(lightColor, darkColor) + regionSampler.setForegroundColors(lightColor, darkColor) } private fun updateTextColorFromRegionSampler() { smartspaceViews.forEach { - val textColor = regionSamplingInstances.getValue(it).currentForegroundColor() + val textColor = regionSamplers.getValue(it).currentForegroundColor() it.setPrimaryTextColor(textColor) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 8a31ed9271ad..470cbcb842c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -198,6 +198,13 @@ class HeadsUpCoordinator @Inject constructor( // At this point we just need to initiate the transfer val summaryUpdate = mPostedEntries[logicalSummary.key] + // Because we now know for certain that some child is going to alert for this summary + // (as we have found a child to transfer the alert to), mark the group as having + // interrupted. This will allow us to know in the future that the "should heads up" + // state of this group has already been handled, just not via the summary entry itself. + logicalSummary.setInterruption() + mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentAlert.key) + // If the summary was not attached, then remove the alert from the detached summary. // Otherwise we can simply ignore its posted update. if (!isSummaryAttached) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index dfaa291c6bb6..473c35d6095a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -69,4 +69,13 @@ class HeadsUpCoordinatorLogger constructor( "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1" }) } + + fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = summaryKey + str2 = childKey + }, { + "marked group summary as interrupted: $str1 for alert transfer to child: $str2" + }) + } } 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 26f0ad9eca87..0554fb5b3689 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 @@ -44,6 +44,7 @@ import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; 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.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -308,6 +309,11 @@ public class NotificationChildrenContainer extends ViewGroup 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); + // It doesn't make sense to keep old animations around, lets cancel them! ExpandableViewState viewState = row.getViewState(); if (viewState != null) { @@ -1377,8 +1383,12 @@ public class NotificationChildrenContainer extends ViewGroup if (child.getVisibility() == View.GONE) { continue; } + child.requestTopRoundness( + /* value = */ 0f, + /* animate = */ isShown(), + SourceType.DefaultValue); child.requestBottomRoundness( - last ? getBottomRoundness() : 0f, + /* value = */ last ? getBottomRoundness() : 0f, /* animate = */ isShown(), SourceType.DefaultValue); last = false; 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 df705c5afeef..2c3330e12229 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 @@ -114,6 +114,8 @@ import com.android.systemui.util.Assert; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.LargeScreenUtils; +import com.google.errorprone.annotations.CompileTimeConstant; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.util.ArrayList; @@ -1383,7 +1385,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable if (height < minExpansionHeight) { mClipRect.left = 0; mClipRect.right = getWidth(); - mClipRect.top = 0; + mClipRect.top = getNotificationsClippingTopBound(); mClipRect.bottom = (int) height; height = minExpansionHeight; setRequestedClipBounds(mClipRect); @@ -1444,6 +1446,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable notifyAppearChangedListeners(); } + private int getNotificationsClippingTopBound() { + if (isHeadsUpTransition()) { + // HUN in split shade can go higher than bottom of NSSL when swiping up so we want + // to give it extra clipping margin. Because clipping has rounded corners, we also + // need to account for that corner clipping. + return -mAmbientState.getStackTopMargin() - mCornerRadius; + } else { + return 0; + } + } + private void notifyAppearChangedListeners() { float appear; float expandAmount; @@ -1482,7 +1495,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable public void updateClipping() { boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode && !mHeadsUpAnimatingAway; - boolean clipToOutline = false; if (mIsClipped != clipped) { mIsClipped = clipped; } @@ -1498,7 +1510,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setClipBounds(null); } - setClipToOutline(clipToOutline); + setClipToOutline(false); } /** @@ -3683,6 +3695,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.INPUT) void handleEmptySpaceClick(MotionEvent ev) { + logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY), + mStatusBarState, mTouchIsClick); switch (ev.getActionMasked()) { case MotionEvent.ACTION_MOVE: final float touchSlop = getTouchSlop(ev); @@ -3694,10 +3708,32 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable case MotionEvent.ACTION_UP: if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { + debugLog("handleEmptySpaceClick: touch event propagated further"); mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); } break; + default: + debugLog("handleEmptySpaceClick: MotionEvent ignored"); + } + } + + private void debugLog(@CompileTimeConstant String s) { + if (mLogger == null) { + return; + } + mLogger.d(s); + } + + private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification, + int statusBarState, boolean touchIsClick) { + if (mLogger == null) { + return; } + mLogger.logEmptySpaceClick( + isTouchBelowLastNotification, + statusBarState, + touchIsClick, + MotionEvent.actionToString(ev.getActionMasked())); } @ShadeViewRefactor(RefactorComponent.INPUT) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt index 4c52db7f8732..64dd6dcd3008 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt @@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.plugins.log.LogBuffer +import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey @@ -10,6 +11,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER +import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject class NotificationStackScrollLogger @Inject constructor( @@ -56,6 +58,25 @@ class NotificationStackScrollLogger @Inject constructor( "key: $str1 expected: $bool1 actual: $bool2" }) } + + fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg) + + fun logEmptySpaceClick( + isBelowLastNotification: Boolean, + statusBarState: Int, + touchIsClick: Boolean, + motionEventDesc: String + ) { + buffer.log(TAG, DEBUG, { + int1 = statusBarState + bool1 = touchIsClick + bool2 = isBelowLastNotification + str1 = motionEventDesc + }, { + "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " + + "isTouchBelowNotification: $bool2 motionEvent: $str1" + }) + } } private const val TAG = "NotificationStackScroll"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 2504fc1c7eb9..1961e1d19bb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -196,8 +196,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void collapsePanelOnMainThread(); - void collapsePanelWithDuration(int duration); - void togglePanel(); void start(); @@ -305,9 +303,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void checkBarModes(); - // Called by NavigationBarFragment - void setQsScrimEnabled(boolean scrimEnabled); - void updateBubblesVisibility(); void setInteracting(int barWindow, boolean interacting); @@ -379,8 +374,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void showKeyguardImpl(); - boolean isInLaunchTransition(); - void fadeKeyguardAfterLaunchTransition(Runnable beforeFading, Runnable endRunnable, Runnable cancelRunnable); @@ -437,8 +430,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn void showPinningEscapeToast(); - KeyguardBottomAreaView getKeyguardBottomAreaView(); - void setBouncerShowing(boolean bouncerShowing); void setBouncerShowingOverDream(boolean bouncerShowingOverDream); @@ -505,12 +496,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn boolean isBouncerShowingOverDream(); - void onBouncerPreHideAnimation(); - boolean isKeyguardSecure(); - NotificationPanelViewController getPanelController(); - NotificationGutsManager getGutsManager(); void updateNotificationPanelTouchState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index d227ed366ecb..acd3d045312d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -466,7 +466,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory; private final PluginManager mPluginManager; - private final com.android.systemui.shade.ShadeController mShadeController; + private final ShadeController mShadeController; private final InitController mInitController; private final PluginDependencyProvider mPluginDependencyProvider; @@ -479,9 +479,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final StatusBarSignalPolicy mStatusBarSignalPolicy; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; - // expanded notifications - // the sliding/resizing panel within the notification window - protected NotificationPanelViewController mNotificationPanelViewController; + /** Controller for the Shade. */ + @VisibleForTesting + NotificationPanelViewController mNotificationPanelViewController; // settings private QSPanelController mQSPanelController; @@ -1152,7 +1152,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { initializer.initializeStatusBar(mCentralSurfacesComponent); mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView); - mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); createNavigationBar(result); @@ -1161,9 +1160,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mLockscreenWallpaper = mLockscreenWallpaperLazy.get(); } - mNotificationPanelViewController.setKeyguardIndicationController( - mKeyguardIndicationController); - mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById( R.id.ambient_indication_container); @@ -2001,8 +1997,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } void makeExpandedInvisible() { - if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible - + " mExpandedVisible=" + mExpandedVisible); + if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); if (!mExpandedVisible || mNotificationShadeWindowView == null) { return; @@ -2178,12 +2173,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNoAnimationOnNextBarModeChange = false; } - // Called by NavigationBarFragment - @Override - public void setQsScrimEnabled(boolean scrimEnabled) { - mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled); - } - /** Temporarily hides Bubbles if the status bar is hidden. */ @Override public void updateBubblesVisibility() { @@ -2559,14 +2548,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */, true /* delayed*/); } else { - // Do it after DismissAction has been processed to conserve the needed // ordering. mMainExecutor.execute(mShadeController::runPostCollapseRunnables); } - } else if (CentralSurfacesImpl.this.isInLaunchTransition() - && mNotificationPanelViewController.isLaunchTransitionFinished()) { - + } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) { // We are not dismissing the shade, but the launch transition is already // finished, // so nobody will call readyForKeyguardDone anymore. Post it such that @@ -2988,11 +2974,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mPresenter.updateMediaMetaData(true /* metaDataChanged */, true); } - @Override - public boolean isInLaunchTransition() { - return mNotificationPanelViewController.isLaunchTransitionFinished(); - } - /** * Fades the content of the keyguard away after the launch transition is done. * @@ -3373,12 +3354,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } - /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */ - @Override - public void collapsePanelWithDuration(int duration) { - mNotificationPanelViewController.collapseWithDuration(duration); - } - /** * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example, * from the power button). @@ -3468,15 +3443,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mNavigationBarController.showPinningEscapeToast(mDisplayId); } - /** - * TODO: Remove this method. Views should not be passed forward. Will cause theme issues. - * @return bottom area view - */ - @Override - public KeyguardBottomAreaView getKeyguardBottomAreaView() { - return mNotificationPanelViewController.getKeyguardBottomAreaView(); - } - protected ViewRootImpl getViewRootImpl() { NotificationShadeWindowView nswv = getNotificationShadeWindowView(); if (nswv != null) return nswv.getViewRootImpl(); @@ -4179,23 +4145,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mBouncerShowingOverDream; } - /** - * When {@link KeyguardBouncer} starts to be dismissed, playing its animation. - */ - @Override - public void onBouncerPreHideAnimation() { - mNotificationPanelViewController.onBouncerPreHideAnimation(); - - } - @Override public boolean isKeyguardSecure() { return mStatusBarKeyguardViewManager.isSecure(); } - @Override - public NotificationPanelViewController getPanelController() { - return mNotificationPanelViewController; - } + // End Extra BaseStatusBarMethods. @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 37f04bb962e8..a28fca1e812e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -64,6 +64,11 @@ public class KeyguardBouncer { private static final String TAG = "KeyguardBouncer"; static final long BOUNCER_FACE_DELAY = 1200; public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f; + /** + * Values for the bouncer expansion represented as the panel expansion. + * Panel expansion 1f = panel fully showing = bouncer fully hidden + * Panel expansion 0f = panel fully hiding = bouncer fully showing + */ public static final float EXPANSION_HIDDEN = 1f; public static final float EXPANSION_VISIBLE = 0f; @@ -143,6 +148,14 @@ public class KeyguardBouncer { } /** + * Get the KeyguardBouncer expansion + * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition. + */ + public float getExpansion() { + return mExpansion; + } + + /** * Enable/disable only the back button */ public void setBackButtonEnabled(boolean enabled) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 5480f5d7489e..93b6437822bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -86,8 +86,10 @@ import com.android.systemui.unfold.SysUIUnfoldComponent; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; @@ -166,13 +168,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onExpansionChanged(float expansion) { - if (mAlternateAuthInterceptor != null) { - mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion); - } if (mBouncerAnimating) { mCentralSurfaces.setBouncerHiddenFraction(expansion); } - updateStates(); } @Override @@ -184,9 +182,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (!isVisible) { mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN); } - if (mAlternateAuthInterceptor != null) { - mAlternateAuthInterceptor.onBouncerVisibilityChanged(); - } /* Register predictive back callback when keyguard becomes visible, and unregister when it's hidden. */ @@ -252,6 +247,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private int mLastBiometricMode; private boolean mLastScreenOffAnimationPlaying; private float mQsExpansion; + final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsModernBouncerEnabled; private OnDismissAction mAfterKeyguardGoneAction; @@ -465,7 +461,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else { - mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) { // Don't expand to the bouncer. Instead transition back to the lock screen (see @@ -475,17 +471,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); } else { - mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE); + mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE); } } else if (mKeyguardStateController.isShowing() && !hideBouncerOverDream) { if (!isWakeAndUnlocking() && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER) - && !mCentralSurfaces.isInLaunchTransition() + && !mNotificationPanelViewController.isLaunchTransitionFinished() && !isUnlockCollapsing()) { if (mBouncer != null) { mBouncer.setExpansion(fraction); } else { - mBouncerInteractor.setExpansion(fraction); + mBouncerInteractor.setPanelExpansion(fraction); } } if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking @@ -504,7 +500,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (mBouncer != null) { mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } else { - mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN); + mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN); } } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) { // Panel expanded while pulsing but didn't translate the bouncer (because we are @@ -849,7 +845,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (isShowing && isOccluding) { SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED); - if (mCentralSurfaces.isInLaunchTransition()) { + if (mNotificationPanelViewController.isLaunchTransitionFinished()) { final Runnable endRunnable = new Runnable() { @Override public void run() { @@ -903,7 +899,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else { mBouncerInteractor.startDisappearAnimation(finishRunnable); } - mCentralSurfaces.onBouncerPreHideAnimation(); + mNotificationPanelViewController.startBouncerPreHideAnimation(); // We update the state (which will show the keyguard) only if an animation will run on // the keyguard. If there is no animation, we wait before updating the state so that we @@ -935,7 +931,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb long uptimeMillis = SystemClock.uptimeMillis(); long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis); - if (mCentralSurfaces.isInLaunchTransition() + if (mNotificationPanelViewController.isLaunchTransitionFinished() || mKeyguardStateController.isFlingingToDismissKeyguard()) { final boolean wasFlingingToDismissKeyguard = mKeyguardStateController.isFlingingToDismissKeyguard(); @@ -1312,7 +1308,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public boolean shouldDisableWindowAnimationsForUnlock() { - return mCentralSurfaces.isInLaunchTransition(); + return mNotificationPanelViewController.isLaunchTransitionFinished(); } @Override @@ -1355,7 +1351,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); } - if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) { + if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) { resetAlternateAuth(false); executeAfterKeyguardGoneAction(); } @@ -1441,6 +1437,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb pw.println(" mPendingWakeupAction: " + mPendingWakeupAction); pw.println(" isBouncerShowing(): " + isBouncerShowing()); pw.println(" bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing()); + pw.println(" Registered KeyguardViewManagerCallbacks:"); + for (KeyguardViewManagerCallback callback : mCallbacks) { + pw.println(" " + callback); + } if (mBouncer != null) { mBouncer.dump(pw); @@ -1465,6 +1465,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** + * Add a callback to listen for changes + */ + public void addCallback(KeyguardViewManagerCallback callback) { + mCallbacks.add(callback); + } + + /** + * Removes callback to stop receiving updates + */ + public void removeCallback(KeyguardViewManagerCallback callback) { + mCallbacks.remove(callback); + } + + /** * Whether qs is currently expanded. */ public float getQsExpansion() { @@ -1476,8 +1490,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb */ public void setQsExpansion(float qsExpansion) { mQsExpansion = qsExpansion; - if (mAlternateAuthInterceptor != null) { - mAlternateAuthInterceptor.setQsExpansion(qsExpansion); + for (KeyguardViewManagerCallback callback : mCallbacks) { + callback.onQSExpansionChanged(mQsExpansion); } } @@ -1491,21 +1505,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer(); } - public boolean isShowingAlternateAuthOrAnimating() { - return mAlternateAuthInterceptor != null - && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer() - || mAlternateAuthInterceptor.isAnimating()); - } - /** - * Forward touches to any alternate authentication affordances. + * Forward touches to callbacks. */ - public boolean onTouch(MotionEvent event) { - if (mAlternateAuthInterceptor == null) { - return false; + public void onTouch(MotionEvent event) { + for (KeyguardViewManagerCallback callback: mCallbacks) { + callback.onTouch(event); } - - return mAlternateAuthInterceptor.onTouch(event); } /** Update keyguard position based on a tapped X coordinate. */ @@ -1639,45 +1645,33 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean isShowingAlternateAuthBouncer(); /** - * print information for the alternate auth interceptor registered - */ - void dump(PrintWriter pw); - - /** - * @return true if the new auth method bouncer is currently animating in or out. - */ - boolean isAnimating(); - - /** - * How much QS is fully expanded where 0f is not showing and 1f is fully expanded. - */ - void setQsExpansion(float qsExpansion); - - /** - * Forward potential touches to authentication interceptor - * @return true if event was handled + * Use when an app occluding the keyguard would like to give the user ability to + * unlock the device using udfps. + * + * @param color of the udfps icon. should have proper contrast with its background. only + * used if requestUdfps = true */ - boolean onTouch(MotionEvent event); + void requestUdfps(boolean requestUdfps, int color); /** - * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully - * hidden + * print information for the alternate auth interceptor registered */ - void setBouncerExpansionChanged(float expansion); + void dump(PrintWriter pw); + } + /** + * Callback for KeyguardViewManager state changes. + */ + public interface KeyguardViewManagerCallback { /** - * called when the bouncer view visibility has changed. + * Set the amount qs is expanded. For example, swipe down from the top of the + * lock screen to start the full QS expansion. */ - void onBouncerVisibilityChanged(); + default void onQSExpansionChanged(float qsExpansion) { } /** - * Use when an app occluding the keyguard would like to give the user ability to - * unlock the device using udfps. - * - * @param color of the udfps icon. should have proper contrast with its background. only - * used if requestUdfps = true + * Forward touch events to callbacks */ - void requestUdfps(boolean requestUdfps, int color); - + default void onTouch(MotionEvent event) { } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt index ee948c04df38..b1642d6addb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -31,7 +31,7 @@ class StatusBarLaunchAnimatorController( delegate.onLaunchAnimationStart(isExpandingFullyAbove) centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true) if (!isExpandingFullyAbove) { - centralSurfaces.collapsePanelWithDuration( + centralSurfaces.notificationPanelViewController.collapseWithDuration( ActivityLaunchAnimator.TIMINGS.totalDuration.toInt()) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt new file mode 100644 index 000000000000..e61890523ebb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt @@ -0,0 +1,27 @@ +/* + * 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.mobile.data.model + +import android.net.NetworkCapabilities + +/** Provides information about a mobile network connection */ +data class MobileConnectivityModel( + /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */ + val isConnected: Boolean = false, + /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */ + val isValidated: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 06e8f467ee0b..581842bc2f57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -16,11 +16,15 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.content.Context +import android.database.ContentObserver +import android.provider.Settings.Global import android.telephony.CellSignalStrength import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState import android.telephony.SignalStrength import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyDisplayInfo import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE @@ -34,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetwork import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange +import com.android.systemui.util.settings.GlobalSettings import java.lang.IllegalStateException import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -42,9 +47,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn /** @@ -65,14 +73,23 @@ interface MobileConnectionRepository { */ val subscriptionModelFlow: Flow<MobileSubscriptionModel> /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */ - val dataEnabled: Flow<Boolean> + val dataEnabled: StateFlow<Boolean> + /** + * True if this connection represents the default subscription per + * [SubscriptionManager.getDefaultDataSubscriptionId] + */ + val isDefaultDataSubscription: StateFlow<Boolean> } @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) class MobileConnectionRepositoryImpl( + private val context: Context, private val subId: Int, private val telephonyManager: TelephonyManager, + private val globalSettings: GlobalSettings, + defaultDataSubId: StateFlow<Int>, + globalMobileDataSettingChangedEvent: Flow<Unit>, bgDispatcher: CoroutineDispatcher, logger: ConnectivityPipelineLogger, scope: CoroutineScope, @@ -86,6 +103,8 @@ class MobileConnectionRepositoryImpl( } } + private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1) + override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run { var state = MobileSubscriptionModel() conflatedCallbackFlow { @@ -165,33 +184,75 @@ class MobileConnectionRepositoryImpl( telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } + .onEach { telephonyCallbackEvent.tryEmit(Unit) } .logOutputChange(logger, "MobileSubscriptionModel") .stateIn(scope, SharingStarted.WhileSubscribed(), state) } + /** Produces whenever the mobile data setting changes for this subId */ + private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + globalSettings.registerContentObserver( + globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"), + /* notifyForDescendants */ true, + observer + ) + + awaitClose { context.contentResolver.unregisterContentObserver(observer) } + } + /** * There are a few cases where we will need to poll [TelephonyManager] so we can update some * internal state where callbacks aren't provided. Any of those events should be merged into * this flow, which can be used to trigger the polling. */ - private val telephonyPollingEvent: Flow<Unit> = subscriptionModelFlow.map {} + private val telephonyPollingEvent: Flow<Unit> = + merge( + telephonyCallbackEvent, + localMobileDataSettingChangedEvent, + globalMobileDataSettingChangedEvent, + ) - override val dataEnabled: Flow<Boolean> = telephonyPollingEvent.map { dataConnectionAllowed() } + override val dataEnabled: StateFlow<Boolean> = + telephonyPollingEvent + .mapLatest { dataConnectionAllowed() } + .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed()) private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed + override val isDefaultDataSubscription: StateFlow<Boolean> = + defaultDataSubId + .mapLatest { it == subId } + .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId) + class Factory @Inject constructor( + private val context: Context, private val telephonyManager: TelephonyManager, private val logger: ConnectivityPipelineLogger, + private val globalSettings: GlobalSettings, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, ) { - fun build(subId: Int): MobileConnectionRepository { + fun build( + subId: Int, + defaultDataSubId: StateFlow<Int>, + globalMobileDataSettingChangedEvent: Flow<Unit>, + ): MobileConnectionRepository { return MobileConnectionRepositoryImpl( + context, subId, telephonyManager.createForSubscriptionId(subId), + globalSettings, + defaultDataSubId, + globalMobileDataSettingChangedEvent, bgDispatcher, logger, scope, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index 0e2428ae393a..c3c1f1403c60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -16,15 +16,27 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.annotation.SuppressLint import android.content.Context import android.content.IntentFilter +import android.database.ContentObserver +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.provider.Settings +import android.provider.Settings.Global.MOBILE_DATA import android.telephony.CarrierConfigManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager import androidx.annotation.VisibleForTesting +import com.android.internal.telephony.PhoneConstants import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings.Config import com.android.systemui.broadcast.BroadcastDispatcher @@ -32,7 +44,9 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger +import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -40,10 +54,12 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -57,13 +73,22 @@ interface MobileConnectionsRepository { val subscriptionsFlow: Flow<List<SubscriptionInfo>> /** Observable for the subscriptionId of the current mobile data connection */ - val activeMobileDataSubscriptionId: Flow<Int> + val activeMobileDataSubscriptionId: StateFlow<Int> /** Observable for [MobileMappings.Config] tracking the defaults */ val defaultDataSubRatConfig: StateFlow<Config> + /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */ + val defaultDataSubId: StateFlow<Int> + + /** The current connectivity status for the default mobile network connection */ + val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> + /** Get or create a repository for the line of service for the given subscription ID */ fun getRepoForSubId(subId: Int): MobileConnectionRepository + + /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */ + val globalMobileDataSettingChangedEvent: Flow<Unit> } @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @@ -72,10 +97,12 @@ interface MobileConnectionsRepository { class MobileConnectionsRepositoryImpl @Inject constructor( + private val connectivityManager: ConnectivityManager, private val subscriptionManager: SubscriptionManager, private val telephonyManager: TelephonyManager, private val logger: ConnectivityPipelineLogger, broadcastDispatcher: BroadcastDispatcher, + private val globalSettings: GlobalSettings, private val context: Context, @Background private val bgDispatcher: CoroutineDispatcher, @Application private val scope: CoroutineScope, @@ -121,17 +148,26 @@ constructor( telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback) awaitClose { telephonyManager.unregisterTelephonyCallback(callback) } } + .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID) + + private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> = + MutableSharedFlow(extraBufferCapacity = 1) + + override val defaultDataSubId: StateFlow<Int> = + broadcastDispatcher + .broadcastFlow( + IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) + ) { intent, _ -> + intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID) + } + .distinctUntilChanged() + .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) } .stateIn( scope, - started = SharingStarted.WhileSubscribed(), - SubscriptionManager.INVALID_SUBSCRIPTION_ID + SharingStarted.WhileSubscribed(), + SubscriptionManager.getDefaultDataSubscriptionId() ) - private val defaultDataSubChangedEvent = - broadcastDispatcher.broadcastFlow( - IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) - ) - private val carrierConfigChangedEvent = broadcastDispatcher.broadcastFlow( IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) @@ -148,9 +184,8 @@ constructor( * This flow will produce whenever the default data subscription or the carrier config changes. */ override val defaultDataSubRatConfig: StateFlow<Config> = - combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ -> - Config.readConfig(context) - } + merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent) + .mapLatest { Config.readConfig(context) } .stateIn( scope, SharingStarted.WhileSubscribed(), @@ -168,6 +203,57 @@ constructor( ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it } } + /** + * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual + * connection repositories also observe the URI for [MOBILE_DATA] + subId. + */ + override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + globalSettings.registerContentObserver( + globalSettings.getUriFor(MOBILE_DATA), + true, + observer + ) + + awaitClose { context.contentResolver.unregisterContentObserver(observer) } + } + + @SuppressLint("MissingPermission") + override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> = + conflatedCallbackFlow { + val callback = + object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) { + override fun onLost(network: Network) { + // Send a disconnected model when lost. Maybe should create a sealed + // type or null here? + trySend(MobileConnectivityModel()) + } + + override fun onCapabilitiesChanged( + network: Network, + caps: NetworkCapabilities + ) { + trySend( + MobileConnectivityModel( + isConnected = caps.hasTransport(TRANSPORT_CELLULAR), + isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED), + ) + ) + } + } + + connectivityManager.registerDefaultNetworkCallback(callback) + + awaitClose { connectivityManager.unregisterNetworkCallback(callback) } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel()) + private fun isValidSubId(subId: Int): Boolean { subscriptionsFlow.value.forEach { if (it.subscriptionId == subId) { @@ -181,7 +267,11 @@ constructor( @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository { - return mobileConnectionRepositoryFactory.build(subId) + return mobileConnectionRepositoryFactory.build( + subId, + defaultDataSubId, + globalMobileDataSettingChangedEvent, + ) } private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt index 77de849691db..91886bb121d5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.mapLatest @@ -40,7 +39,7 @@ import kotlinx.coroutines.withContext */ interface UserSetupRepository { /** Observable tracking [DeviceProvisionedController.isUserSetup] */ - val isUserSetupFlow: Flow<Boolean> + val isUserSetupFlow: StateFlow<Boolean> } @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index f99d278c3903..0da84f0bec9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -18,81 +18,109 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CarrierConfigManager import com.android.settingslib.SignalIcon.MobileIconGroup +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.util.CarrierConfigTracker -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn interface MobileIconInteractor { + /** Only true if mobile is the default transport but is not validated, otherwise false */ + val isDefaultConnectionFailed: StateFlow<Boolean> + + /** True when telephony tells us that the data state is CONNECTED */ + val isDataConnected: StateFlow<Boolean> + + // TODO(b/256839546): clarify naming of default vs active + /** True if we want to consider the data connection enabled */ + val isDefaultDataEnabled: StateFlow<Boolean> + /** Observable for the data enabled state of this connection */ - val isDataEnabled: Flow<Boolean> + val isDataEnabled: StateFlow<Boolean> /** Observable for RAT type (network type) indicator */ - val networkTypeIconGroup: Flow<MobileIconGroup> + val networkTypeIconGroup: StateFlow<MobileIconGroup> /** True if this line of service is emergency-only */ - val isEmergencyOnly: Flow<Boolean> + val isEmergencyOnly: StateFlow<Boolean> /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */ - val level: Flow<Int> + val level: StateFlow<Int> /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */ - val numberOfLevels: Flow<Int> - - /** True when we want to draw an icon that makes room for the exclamation mark */ - val cutOut: Flow<Boolean> + val numberOfLevels: StateFlow<Int> } /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) class MobileIconInteractorImpl( - defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>, - defaultMobileIconGroup: Flow<MobileIconGroup>, + @Application scope: CoroutineScope, + defaultSubscriptionHasDataEnabled: StateFlow<Boolean>, + defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>, + defaultMobileIconGroup: StateFlow<MobileIconGroup>, + override val isDefaultConnectionFailed: StateFlow<Boolean>, mobileMappingsProxy: MobileMappingsProxy, connectionRepository: MobileConnectionRepository, ) : MobileIconInteractor { private val mobileStatusInfo = connectionRepository.subscriptionModelFlow - override val isDataEnabled: Flow<Boolean> = connectionRepository.dataEnabled + override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled + + override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled /** Observable for the current RAT indicator icon ([MobileIconGroup]) */ - override val networkTypeIconGroup: Flow<MobileIconGroup> = + override val networkTypeIconGroup: StateFlow<MobileIconGroup> = combine( - mobileStatusInfo, - defaultMobileIconMapping, - defaultMobileIconGroup, - ) { info, mapping, defaultGroup -> - val lookupKey = - when (val resolved = info.resolvedNetworkType) { - is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type) - is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type) + mobileStatusInfo, + defaultMobileIconMapping, + defaultMobileIconGroup, + ) { info, mapping, defaultGroup -> + val lookupKey = + when (val resolved = info.resolvedNetworkType) { + is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type) + is OverrideNetworkType -> + mobileMappingsProxy.toIconKeyOverride(resolved.type) + } + mapping[lookupKey] ?: defaultGroup + } + .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value) + + override val isEmergencyOnly: StateFlow<Boolean> = + mobileStatusInfo + .mapLatest { it.isEmergencyOnly } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val level: StateFlow<Int> = + mobileStatusInfo + .mapLatest { mobileModel -> + // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi] + if (mobileModel.isGsm) { + mobileModel.primaryLevel + } else { + mobileModel.cdmaLevel } - mapping[lookupKey] ?: defaultGroup - } - - override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly } - - override val level: Flow<Int> = - mobileStatusInfo.map { mobileModel -> - // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi] - if (mobileModel.isGsm) { - mobileModel.primaryLevel - } else { - mobileModel.cdmaLevel } - } + .stateIn(scope, SharingStarted.WhileSubscribed(), 0) /** * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL] * once it's wired up inside of [CarrierConfigTracker] */ - override val numberOfLevels: Flow<Int> = flowOf(4) + override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4) - /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */ - // TODO: find a better name for this? - override val cutOut: Flow<Boolean> = flowOf(false) + override val isDataConnected: StateFlow<Boolean> = + mobileStatusInfo + .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index 614d583c3c48..a4175c3a6ab1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.CarrierConfigManager import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.dagger.SysUISingleton @@ -35,7 +36,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn /** @@ -51,12 +54,16 @@ import kotlinx.coroutines.flow.stateIn interface MobileIconsInteractor { /** List of subscriptions, potentially filtered for CBRS */ val filteredSubscriptions: Flow<List<SubscriptionInfo>> + /** True if the active mobile data subscription has data enabled */ + val activeDataConnectionHasDataEnabled: StateFlow<Boolean> /** The icon mapping from network type to [MobileIconGroup] for the default subscription */ - val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> + val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */ - val defaultMobileIconGroup: Flow<MobileIconGroup> + val defaultMobileIconGroup: StateFlow<MobileIconGroup> + /** True only if the default network is mobile, and validation also failed */ + val isDefaultConnectionFailed: StateFlow<Boolean> /** True once the user has been set up */ - val isUserSetup: Flow<Boolean> + val isUserSetup: StateFlow<Boolean> /** * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given * subId. Will throw if the ID is invalid @@ -79,6 +86,22 @@ constructor( private val activeMobileDataSubscriptionId = mobileConnectionsRepo.activeMobileDataSubscriptionId + private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> = + activeMobileDataSubscriptionId + .mapLatest { activeId -> + if (activeId == INVALID_SUBSCRIPTION_ID) { + null + } else { + mobileConnectionsRepo.getRepoForSubId(activeId) + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> = + activeMobileDataConnectionRepo + .flatMapLatest { it?.dataEnabled ?: flowOf(false) } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> = mobileConnectionsRepo.subscriptionsFlow @@ -132,22 +155,40 @@ constructor( */ override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> = mobileConnectionsRepo.defaultDataSubRatConfig - .map { mobileMappingsProxy.mapIconSets(it) } + .mapLatest { mobileMappingsProxy.mapIconSets(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf()) /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */ override val defaultMobileIconGroup: StateFlow<MobileIconGroup> = mobileConnectionsRepo.defaultDataSubRatConfig - .map { mobileMappingsProxy.getDefaultIcons(it) } + .mapLatest { mobileMappingsProxy.getDefaultIcons(it) } .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G) - override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow + /** + * We want to show an error state when cellular has actually failed to validate, but not if some + * other transport type is active, because then we expect there not to be validation. + */ + override val isDefaultConnectionFailed: StateFlow<Boolean> = + mobileConnectionsRepo.defaultMobileNetworkConnectivity + .mapLatest { connectivityModel -> + if (!connectivityModel.isConnected) { + false + } else { + !connectivityModel.isValidated + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow /** Vends out new [MobileIconInteractor] for a particular subId */ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = MobileIconInteractorImpl( + scope, + activeDataConnectionHasDataEnabled, defaultMobileIconMapping, defaultMobileIconGroup, + isDefaultConnectionFailed, mobileMappingsProxy, mobileConnectionsRepo.getRepoForSubId(subId), ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 81317398f086..7869021c0501 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -24,10 +24,12 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.mapLatest /** * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over @@ -39,29 +41,38 @@ import kotlinx.coroutines.flow.flowOf * * TODO: figure out where carrier merged and VCN models go (probably here?) */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) class MobileIconViewModel constructor( val subscriptionId: Int, iconInteractor: MobileIconInteractor, logger: ConnectivityPipelineLogger, ) { + /** Whether or not to show the error state of [SignalDrawable] */ + private val showExclamationMark: Flow<Boolean> = + iconInteractor.isDefaultDataEnabled.mapLatest { !it } + /** An int consumable by [SignalDrawable] for display */ - var iconId: Flow<Int> = - combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) { + val iconId: Flow<Int> = + combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) { level, numberOfLevels, - cutOut -> - SignalDrawable.getState(level, numberOfLevels, cutOut) + showExclamationMark -> + SignalDrawable.getState(level, numberOfLevels, showExclamationMark) } .distinctUntilChanged() .logOutputChange(logger, "iconId($subscriptionId)") /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */ - var networkTypeIcon: Flow<Icon?> = - combine(iconInteractor.networkTypeIconGroup, iconInteractor.isDataEnabled) { - networkTypeIconGroup, - isDataEnabled -> - if (!isDataEnabled) { + val networkTypeIcon: Flow<Icon?> = + combine( + iconInteractor.networkTypeIconGroup, + iconInteractor.isDataConnected, + iconInteractor.isDataEnabled, + iconInteractor.isDefaultConnectionFailed, + ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection -> + if (!dataConnected || !dataEnabled || failedConnection) { null } else { val desc = @@ -72,5 +83,5 @@ constructor( } } - var tint: Flow<Int> = flowOf(Color.CYAN) + val tint: Flow<Int> = flowOf(Color.CYAN) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt index bc2ae64dd946..e3266115f777 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt @@ -67,7 +67,7 @@ public class DeviceControlsControllerImpl @Inject constructor( internal const val QS_DEFAULT_POSITION = 7 internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted" - internal const val PREFS_CONTROLS_FILE = "controls_prefs" + const val PREFS_CONTROLS_FILE = "controls_prefs" internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts" private const val SEEDING_MAX = 2 } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 3ecb15b9d79c..5894fd385ee3 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -115,7 +115,6 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { private final SecureSettings mSecureSettings; private final Executor mMainExecutor; private final Handler mBgHandler; - private final boolean mIsMonochromaticEnabled; private final Context mContext; private final boolean mIsMonetEnabled; private final UserTracker mUserTracker; @@ -365,7 +364,6 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags, @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) { mContext = context; - mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES); mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET); mDeviceProvisionedController = deviceProvisionedController; mBroadcastDispatcher = broadcastDispatcher; @@ -669,11 +667,8 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { // used as a system-wide theme. // - Content intentionally excluded, intended for media player, not system-wide List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ, - Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT)); - - if (mIsMonochromaticEnabled) { - validStyles.add(Style.MONOCHROMATIC); - } + Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT, + Style.MONOCHROMATIC)); Style style = mThemeStyle; final String overlayPackageJson = mSecureSettings.getStringForUser( diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 10a09dd169e8..61eadeb1764f 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -44,6 +44,7 @@ import com.android.systemui.qs.tileimpl.QSFactoryImpl; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsImplementation; import com.android.systemui.screenshot.ReferenceScreenshotModule; +import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; @@ -89,6 +90,7 @@ import dagger.multibindings.IntoSet; includes = { AospPolicyModule.class, GestureModule.class, + MultiUserUtilsModule.class, PowerModule.class, QSModule.class, ReferenceScreenshotModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index b16dc5403a57..6a2326036ec0 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -62,6 +62,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext /** @@ -136,7 +137,7 @@ constructor( private val isNewImpl: Boolean get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER) - private val _userSwitcherSettings = MutableStateFlow<UserSwitcherSettingsModel?>(null) + private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() }) override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> = _userSwitcherSettings.asStateFlow().filterNotNull() @@ -235,7 +236,7 @@ constructor( } override fun isSimpleUserSwitcher(): Boolean { - return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher) + return _userSwitcherSettings.value.isSimpleUserSwitcher } private fun observeSelectedUser() { diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt index 07e5cf9d9df2..f9d14cd1b7ca 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt @@ -208,7 +208,12 @@ constructor( if (newGuestId == UserHandle.USER_NULL) { Log.e(TAG, "Could not create new guest, switching back to system user") switchUser(UserHandle.USER_SYSTEM) - withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) } + withContext(backgroundDispatcher) { + manager.removeUserWhenPossible( + UserHandle.of(currentUser.id), + /* overrideDevicePolicy= */ false + ) + } try { WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null) } catch (e: RemoteException) { @@ -222,13 +227,21 @@ constructor( switchUser(newGuestId) - withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) } + withContext(backgroundDispatcher) { + manager.removeUserWhenPossible( + UserHandle.of(currentUser.id), + /* overrideDevicePolicy= */ false + ) + } } else { if (repository.isGuestUserAutoCreated) { repository.isGuestUserResetting = true } switchUser(targetUserId) - manager.removeUser(currentUser.id) + manager.removeUserWhenPossible( + UserHandle.of(currentUser.id), + /* overrideDevicePolicy= */ false + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt index 968af59e6c45..ad09ee3c10d9 100644 --- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt @@ -61,14 +61,15 @@ object UserSwitcherViewBinder { falsingCollector: FalsingCollector, onFinish: () -> Unit, ) { - val rootView: UserSwitcherRootView = view.requireViewById(R.id.user_switcher_root) - val flowWidget: FlowWidget = view.requireViewById(R.id.flow) + val gridContainerView: UserSwitcherRootView = + view.requireViewById(R.id.user_switcher_grid_container) + val flowWidget: FlowWidget = gridContainerView.requireViewById(R.id.flow) val addButton: View = view.requireViewById(R.id.add) val cancelButton: View = view.requireViewById(R.id.cancel) val popupMenuAdapter = MenuAdapter(layoutInflater) var popupMenu: UserSwitcherPopupMenu? = null - rootView.touchHandler = + gridContainerView.touchHandler = object : Gefingerpoken { override fun onTouchEvent(ev: MotionEvent?): Boolean { falsingCollector.onTouchEvent(ev) @@ -134,7 +135,7 @@ object UserSwitcherViewBinder { val viewPool = view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList() viewPool.forEach { - view.removeView(it) + gridContainerView.removeView(it) flowWidget.removeView(it) } users.forEach { userViewModel -> @@ -152,7 +153,7 @@ object UserSwitcherViewBinder { inflatedView } userView.id = View.generateViewId() - view.addView(userView) + gridContainerView.addView(userView) flowWidget.addView(userView) UserViewBinder.bind( view = userView, diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index 44f6d03207b1..ad97ef4a79bc 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -31,6 +31,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.os.Trace; +import android.os.UserHandle; import android.service.wallpaper.WallpaperService; import android.util.ArraySet; import android.util.Log; @@ -598,7 +599,6 @@ public class ImageWallpaper extends WallpaperService { getDisplayContext().getSystemService(DisplayManager.class) .unregisterDisplayListener(this); mWallpaperLocalColorExtractor.cleanUp(); - unloadBitmap(); } @Override @@ -676,9 +676,14 @@ public class ImageWallpaper extends WallpaperService { void drawFrameOnCanvas(Bitmap bitmap) { Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame"); Surface surface = mSurfaceHolder.getSurface(); - Canvas canvas = mWideColorGamut - ? surface.lockHardwareWideColorGamutCanvas() - : surface.lockHardwareCanvas(); + Canvas canvas = null; + try { + canvas = mWideColorGamut + ? surface.lockHardwareWideColorGamutCanvas() + : surface.lockHardwareCanvas(); + } catch (IllegalStateException e) { + Log.w(TAG, "Unable to lock canvas", e); + } if (canvas != null) { Rect dest = mSurfaceHolder.getSurfaceFrame(); try { @@ -709,17 +714,6 @@ public class ImageWallpaper extends WallpaperService { } } - private void unloadBitmap() { - mBackgroundExecutor.execute(this::unloadBitmapSynchronized); - } - - private void unloadBitmapSynchronized() { - synchronized (mLock) { - mBitmapUsages = 0; - unloadBitmapInternal(); - } - } - private void unloadBitmapInternal() { Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap"); if (mBitmap != null) { @@ -738,7 +732,7 @@ public class ImageWallpaper extends WallpaperService { boolean loadSuccess = false; Bitmap bitmap; try { - bitmap = mWallpaperManager.getBitmap(false); + bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false); if (bitmap != null && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) { throw new RuntimeException("Wallpaper is too large to draw!"); @@ -757,7 +751,7 @@ public class ImageWallpaper extends WallpaperService { } try { - bitmap = mWallpaperManager.getBitmap(false); + bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false); } catch (RuntimeException | OutOfMemoryError e) { Log.w(TAG, "Unable to load default wallpaper!", e); bitmap = null; @@ -770,9 +764,6 @@ public class ImageWallpaper extends WallpaperService { Log.e(TAG, "Attempt to load a recycled bitmap"); } else if (mBitmap == bitmap) { Log.e(TAG, "Loaded a bitmap that was already loaded"); - } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) { - Log.e(TAG, "Attempt to load an invalid wallpaper of length " - + bitmap.getWidth() + "x" + bitmap.getHeight()); } else { // at this point, loading is done correctly. loadSuccess = true; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 1c3656d71d82..52b6b38ca8ef 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -65,7 +65,6 @@ import org.mockito.junit.MockitoJUnit class ClockEventControllerTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() - @Mock private lateinit var keyguardInteractor: KeyguardInteractor @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher @Mock private lateinit var batteryController: BatteryController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt index aca60c033bac..131cf7d33e3a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt @@ -72,6 +72,7 @@ private fun fingerprintModel(user: Int) = KeyguardFingerprintListenModel( keyguardOccluded = false, occludingAppRequestingFp = false, primaryUser = false, + shouldListenSfpsState = false, shouldListenForFingerprintAssistant = false, switchingUser = false, udfps = false, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index f9bec65ab677..52f8ef8b7ebc 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -55,6 +55,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.SidefpsController; +import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.SessionTracker; @@ -143,6 +144,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private SidefpsController mSidefpsController; @Mock private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock; + @Mock + private FalsingA11yDelegate mFalsingA11yDelegate; @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback; @@ -186,7 +189,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardStateController, mKeyguardSecurityViewFlipperController, mConfigurationController, mFalsingCollector, mFalsingManager, mUserSwitcherController, mFeatureFlags, mGlobalSettings, - mSessionTracker, Optional.of(mSidefpsController)).create(mSecurityCallback); + mSessionTracker, Optional.of(mSidefpsController), mFalsingA11yDelegate).create( + mSecurityCallback); } @Test @@ -225,7 +229,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.updateResources(); verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), + eq(mFalsingA11yDelegate)); // Update rotation. Should trigger update mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; @@ -233,7 +238,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.updateResources(); verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), + eq(mFalsingA11yDelegate)); } private void touchDown() { @@ -269,7 +275,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), + eq(mFalsingA11yDelegate)); } @Test @@ -282,7 +289,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), + eq(mFalsingA11yDelegate)); } @Test @@ -293,7 +301,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), - any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class)); + any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), + eq(mFalsingA11yDelegate)); } @Test @@ -307,7 +316,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class), any(UserSwitcherController.class), - captor.capture()); + captor.capture(), + eq(mFalsingA11yDelegate)); captor.getValue().showUnlockToContinueMessage(); verify(mKeyguardPasswordViewControllerMock).showMessage( getContext().getString(R.string.keyguard_unlock_to_continue), null); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java index 82d3ca785161..1bd14e558fa0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java @@ -31,6 +31,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT; import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; +import static com.android.keyguard.KeyguardSecurityContainer.MODE_USER_SWITCHER; import static com.google.common.truth.Truth.assertThat; @@ -54,6 +55,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.user.data.source.UserRecord; @@ -87,6 +89,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private FalsingManager mFalsingManager; @Mock private UserSwitcherController mUserSwitcherController; + @Mock + private FalsingA11yDelegate mFalsingA11yDelegate; private KeyguardSecurityContainer mKeyguardSecurityContainer; @@ -111,15 +115,14 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User"); when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true); } + @Test public void testOnApplyWindowInsets() { int paddingBottom = getContext().getResources() .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin); int imeInsetAmount = paddingBottom + 1; int systemBarInsetAmount = 0; - - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController, () -> {}); + initMode(MODE_DEFAULT); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -140,8 +143,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin); int systemBarInsetAmount = paddingBottom + 1; - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController, () -> {}); + initMode(MODE_DEFAULT); Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount); Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount); @@ -157,11 +159,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { @Test public void testDefaultViewMode() { - mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager, - mUserSwitcherController, () -> { - }); - mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager, - mUserSwitcherController, () -> {}); + initMode(MODE_ONE_HANDED); + initMode(MODE_DEFAULT); ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(mSecurityViewFlipper.getId()); assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID); @@ -377,8 +376,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupUserSwitcher() { when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT); - mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER, - mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {}); + initMode(MODE_USER_SWITCHER); } private ArrayList<UserRecord> buildUserRecords(int count) { @@ -396,8 +394,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { private void setupForUpdateKeyguardPosition(boolean oneHandedMode) { int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT; - mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager, - mUserSwitcherController, () -> {}); + initMode(mode); } /** Get the ConstraintLayout constraint of the view. */ @@ -406,4 +403,10 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase { constraintSet.clone(mKeyguardSecurityContainer); return constraintSet.getConstraint(viewId); } + + private void initMode(int mode) { + mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager, + mUserSwitcherController, () -> { + }, mFalsingA11yDelegate); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 680c3b8f546b..5718835a1a95 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT; +import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON; import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE; import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID; @@ -39,6 +40,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -54,6 +56,7 @@ import android.app.trust.IStrongAuthTracker; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -61,18 +64,21 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; +import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback; +import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceManager; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.net.Uri; import android.nfc.NfcAdapter; import android.os.Bundle; import android.os.CancellationSignal; @@ -111,6 +117,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.settings.SecureSettings; import org.junit.After; import org.junit.Assert; @@ -182,6 +189,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock + private SecureSettings mSecureSettings; + @Mock private TelephonyManager mTelephonyManager; @Mock private SensorPrivacyManager mSensorPrivacyManager; @@ -215,6 +224,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private GlobalSettings mGlobalSettings; private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig; + private final int mCurrentUserId = 100; private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0); @@ -224,6 +234,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Captor private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor; + @Mock + private Uri mURI; + // Direct executor private final Executor mBackgroundExecutor = Runnable::run; private final Executor mMainExecutor = Runnable::run; @@ -305,6 +318,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); + + when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI); + + final ContentResolver contentResolver = mContext.getContentResolver(); + ExtendedMockito.spyOn(contentResolver); + doNothing().when(contentResolver) + .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class), + anyInt()); + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext); verify(mBiometricManager) @@ -1136,6 +1158,63 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled() + throws RemoteException { + // SFPS supported and enrolled + final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>(); + props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON)); + when(mAuthController.getSfpsProps()).thenReturn(props); + when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true); + + // WHEN require screen on to auth is disabled, and keyguard is not awake + when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0); + mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref(); + + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true); + + // Preconditions for sfps auth to run + keyguardNotGoingAway(); + currentUserIsPrimary(); + currentUserDoesNotHaveTrust(); + biometricsNotDisabledThroughDevicePolicyManager(); + biometricsEnabledForCurrentUser(); + userNotCurrentlySwitching(); + + statusBarShadeIsLocked(); + mTestableLooper.processAllMessages(); + + // THEN we should listen for sfps when screen off, because require screen on is disabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + + // WHEN require screen on to auth is enabled, and keyguard is not awake + when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1); + mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref(); + + // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse(); + + // Device now awake & keyguard is now interactive + deviceNotGoingToSleep(); + deviceIsInteractive(); + keyguardIsVisible(); + + // THEN we should listen for sfps when screen on, and require screen on is enabled + assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue(); + } + + private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal( + @FingerprintSensorProperties.SensorType int sensorType) { + return new FingerprintSensorPropertiesInternal( + 0 /* sensorId */, + SensorProperties.STRENGTH_STRONG, + 1 /* maxEnrollmentsPerUser */, + new ArrayList<ComponentInfoInternal>(), + sensorType, + true /* resetLockoutRequiresHardwareAuthToken */); + } + + @Test public void testShouldNotListenForUdfps_whenTrustEnabled() { // GIVEN a "we should listen for udfps" state mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD); @@ -1804,7 +1883,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { protected TestableKeyguardUpdateMonitor(Context context) { super(context, TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(), - mBroadcastDispatcher, mDumpManager, + mBroadcastDispatcher, mSecureSettings, mDumpManager, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java index 8ef65dcb2c3a..a36105e11514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.floatingmenu; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; @@ -47,7 +48,7 @@ public class DismissAnimationControllerTest extends SysuiTestCase { stubWindowManager); final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance); - mDismissView = new DismissView(mContext); + mDismissView = spy(new DismissView(mContext)); mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index d489656559c3..6ec5a6cc799d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -42,6 +42,8 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -103,6 +105,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsView: UdfpsView @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator + @Mock private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var bouncerInteractor: BouncerInteractor @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -136,7 +140,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, configurationController, systemClock, keyguardStateController, unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason, - controllerCallback, onTouch, activityLaunchAnimator, isDebuggable + controllerCallback, onTouch, activityLaunchAnimator, featureFlags, + bouncerInteractor, isDebuggable ) block() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index ed957db2852b..41c307a157a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -71,6 +71,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.ScreenLifecycle; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeExpansionStateManager; @@ -192,6 +193,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private ActivityLaunchAnimator mActivityLaunchAnimator; @Mock private AlternateUdfpsTouchProvider mAlternateTouchProvider; + @Mock + private BouncerInteractor mBouncerInteractor; // Capture listeners so that they can be used to send events @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor; @@ -272,7 +275,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mLatencyTracker, mActivityLaunchAnimator, Optional.of(mAlternateTouchProvider), - mBiometricsExecutor); + mBiometricsExecutor, + mBouncerInteractor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java index 7864f21bd1d6..1bc237d422d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; @@ -48,7 +48,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { @Mock private AuthController mAuthController; @Mock - private IUdfpsHbmListener mDisplayCallback; + private IUdfpsRefreshRateRequestCallback mDisplayCallback; @Mock private UdfpsLogger mUdfpsLogger; @Mock @@ -68,7 +68,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID); // Set up mocks. - when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback); + when(mAuthController.getUdfpsRefreshRateCallback()).thenReturn(mDisplayCallback); // Create a real controller with mock dependencies. mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController, @@ -81,7 +81,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { mHbmController.enable(mOnEnabled); // Should set the appropriate refresh rate for UDFPS and notify the caller. - verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID)); + verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID)); verify(mOnEnabled).run(); // Disable the UDFPS mode. @@ -89,7 +89,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { // Should unset the refresh rate and notify the caller. verify(mOnDisabled).run(); - verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID)); + verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID)); } @Test @@ -98,7 +98,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { mHbmController.enable(mOnEnabled); // Should set the appropriate refresh rate for UDFPS and notify the caller. - verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID)); + verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID)); verify(mOnEnabled).run(); // Second request to enable the UDFPS mode, while it's still enabled. @@ -115,7 +115,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { mHbmController.enable(mOnEnabled); // Should set the appropriate refresh rate for UDFPS and notify the caller. - verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID)); + verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID)); verify(mOnEnabled).run(); // First request to disable the UDFPS mode. @@ -123,7 +123,7 @@ public class UdfpsDisplayModeTest extends SysuiTestCase { // Should unset the refresh rate and notify the caller. verify(mOnDisabled).run(); - verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID)); + verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID)); // Second request to disable the UDFPS mode, when it's already disabled. mHbmController.disable(mOnDisabled); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java new file mode 100644 index 000000000000..e5c7a42c06a6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.shade.ShadeExpansionChangeEvent; +import com.android.systemui.shade.ShadeExpansionListener; +import com.android.systemui.shade.ShadeExpansionStateManager; +import com.android.systemui.statusbar.LockscreenShadeTransitionController; +import com.android.systemui.statusbar.phone.KeyguardBouncer; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; +import com.android.systemui.statusbar.phone.SystemUIDialogManager; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase { + // Dependencies + protected @Mock UdfpsKeyguardView mView; + protected @Mock Context mResourceContext; + protected @Mock StatusBarStateController mStatusBarStateController; + protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager; + protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController; + protected @Mock DumpManager mDumpManager; + protected @Mock DelayableExecutor mExecutor; + protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; + protected @Mock KeyguardStateController mKeyguardStateController; + protected @Mock KeyguardViewMediator mKeyguardViewMediator; + protected @Mock ConfigurationController mConfigurationController; + protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + protected @Mock SystemUIDialogManager mDialogManager; + protected @Mock UdfpsController mUdfpsController; + protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator; + protected @Mock KeyguardBouncer mBouncer; + protected @Mock BouncerInteractor mBouncerInteractor; + + protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + protected FakeSystemClock mSystemClock = new FakeSystemClock(); + + protected UdfpsKeyguardViewController mController; + + // Capture listeners so that they can be used to send events + private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; + protected StatusBarStateController.StateListener mStatusBarStateListener; + + private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor; + protected List<ShadeExpansionListener> mExpansionListeners; + + private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor> + mAltAuthInterceptorCaptor; + protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor; + + private @Captor ArgumentCaptor<KeyguardStateController.Callback> + mKeyguardStateControllerCallbackCaptor; + protected KeyguardStateController.Callback mKeyguardStateControllerCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mView.getContext()).thenReturn(mResourceContext); + when(mResourceContext.getString(anyInt())).thenReturn("test string"); + when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false); + when(mView.getUnpausedAlpha()).thenReturn(255); + mController = createUdfpsKeyguardViewController(); + } + + protected void sendStatusBarStateChanged(int statusBarState) { + mStatusBarStateListener.onStateChanged(statusBarState); + } + + protected void captureStatusBarStateListeners() { + verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture()); + mStatusBarStateListener = mStateListenerCaptor.getValue(); + } + + protected void captureStatusBarExpansionListeners() { + verify(mShadeExpansionStateManager, times(2)) + .addExpansionListener(mExpansionListenerCaptor.capture()); + // first (index=0) is from super class, UdfpsAnimationViewController. + // second (index=1) is from UdfpsKeyguardViewController + mExpansionListeners = mExpansionListenerCaptor.getAllValues(); + } + + protected void updateStatusBarExpansion(float fraction, boolean expanded) { + ShadeExpansionChangeEvent event = + new ShadeExpansionChangeEvent( + fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f); + for (ShadeExpansionListener listener : mExpansionListeners) { + listener.onPanelExpansionChanged(event); + } + } + + protected void captureAltAuthInterceptor() { + verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor( + mAltAuthInterceptorCaptor.capture()); + mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue(); + } + + protected void captureKeyguardStateControllerCallback() { + verify(mKeyguardStateController).addCallback( + mKeyguardStateControllerCallbackCaptor.capture()); + mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); + } + + public UdfpsKeyguardViewController createUdfpsKeyguardViewController() { + return createUdfpsKeyguardViewController(false); + } + + protected UdfpsKeyguardViewController createUdfpsKeyguardViewController( + boolean useModernBouncer) { + mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer); + when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn( + useModernBouncer ? null : mBouncer); + return new UdfpsKeyguardViewController( + mView, + mStatusBarStateController, + mShadeExpansionStateManager, + mStatusBarKeyguardViewManager, + mKeyguardUpdateMonitor, + mDumpManager, + mLockscreenShadeTransitionController, + mConfigurationController, + mSystemClock, + mKeyguardStateController, + mUnlockedScreenOffAnimationController, + mDialogManager, + mUdfpsController, + mActivityLaunchAnimator, + mFeatureFlags, + mBouncerInteractor); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java index c0f9c82fb131..55b61948ee45 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -25,125 +25,52 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.eq; 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; -import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityLaunchAnimator; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.ShadeExpansionChangeEvent; import com.android.systemui.shade.ShadeExpansionListener; -import com.android.systemui.shade.ShadeExpansionStateManager; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.DelayableExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Before; +import com.android.systemui.statusbar.phone.KeyguardBouncer; + 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.List; @SmallTest @RunWith(AndroidTestingRunner.class) @RunWithLooper -public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { - // Dependencies - @Mock - private UdfpsKeyguardView mView; - @Mock - private Context mResourceContext; - @Mock - private StatusBarStateController mStatusBarStateController; - @Mock - private ShadeExpansionStateManager mShadeExpansionStateManager; - @Mock - private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - @Mock - private LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Mock - private DumpManager mDumpManager; - @Mock - private DelayableExecutor mExecutor; - @Mock - private KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock - private KeyguardStateController mKeyguardStateController; - @Mock - private KeyguardViewMediator mKeyguardViewMediator; - @Mock - private ConfigurationController mConfigurationController; - @Mock - private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - @Mock - private SystemUIDialogManager mDialogManager; - @Mock - private UdfpsController mUdfpsController; - @Mock - private ActivityLaunchAnimator mActivityLaunchAnimator; - private FakeSystemClock mSystemClock = new FakeSystemClock(); - - private UdfpsKeyguardViewController mController; - - // Capture listeners so that they can be used to send events - @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; - private StatusBarStateController.StateListener mStatusBarStateListener; - - @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor; - private List<ShadeExpansionListener> mExpansionListeners; - - @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor> - mAltAuthInterceptorCaptor; - private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor; - - @Captor private ArgumentCaptor<KeyguardStateController.Callback> - mKeyguardStateControllerCallbackCaptor; - private KeyguardStateController.Callback mKeyguardStateControllerCallback; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - when(mView.getContext()).thenReturn(mResourceContext); - when(mResourceContext.getString(anyInt())).thenReturn("test string"); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false); - when(mView.getUnpausedAlpha()).thenReturn(255); - mController = new UdfpsKeyguardViewController( - mView, - mStatusBarStateController, - mShadeExpansionStateManager, - mStatusBarKeyguardViewManager, - mKeyguardUpdateMonitor, - mDumpManager, - mLockscreenShadeTransitionController, - mConfigurationController, - mSystemClock, - mKeyguardStateController, - mUnlockedScreenOffAnimationController, - mDialogManager, - mUdfpsController, - mActivityLaunchAnimator); +public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest { + private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> + mBouncerExpansionCallbackCaptor; + private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback; + + @Override + public UdfpsKeyguardViewController createUdfpsKeyguardViewController() { + return createUdfpsKeyguardViewController(/* useModernBouncer */ false); + } + + @Test + public void testShouldPauseAuth_bouncerShowing() { + mController.onViewAttached(); + captureStatusBarStateListeners(); + sendStatusBarStateChanged(StatusBarState.KEYGUARD); + + captureBouncerExpansionCallback(); + when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); + when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true); + mBouncerExpansionCallback.onVisibilityChanged(true); + + assertTrue(mController.shouldPauseAuth()); } + + @Test public void testRegistersExpansionChangedListenerOnAttached() { mController.onViewAttached(); @@ -202,20 +129,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { } @Test - public void testShouldPauseAuthBouncerShowing() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - captureAltAuthInterceptor(); - when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); - when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true); - mAltAuthInterceptor.onBouncerVisibilityChanged(); - - assertTrue(mController.shouldPauseAuth()); - } - - @Test public void testShouldPauseAuthUnpausedAlpha0() { mController.onViewAttached(); captureStatusBarStateListeners(); @@ -503,41 +416,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase { verify(mView, atLeastOnce()).setPauseAuth(false); } - private void sendStatusBarStateChanged(int statusBarState) { - mStatusBarStateListener.onStateChanged(statusBarState); - } - - private void captureStatusBarStateListeners() { - verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture()); - mStatusBarStateListener = mStateListenerCaptor.getValue(); - } - - private void captureStatusBarExpansionListeners() { - verify(mShadeExpansionStateManager, times(2)) - .addExpansionListener(mExpansionListenerCaptor.capture()); - // first (index=0) is from super class, UdfpsAnimationViewController. - // second (index=1) is from UdfpsKeyguardViewController - mExpansionListeners = mExpansionListenerCaptor.getAllValues(); - } - - private void updateStatusBarExpansion(float fraction, boolean expanded) { - ShadeExpansionChangeEvent event = - new ShadeExpansionChangeEvent( - fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f); - for (ShadeExpansionListener listener : mExpansionListeners) { - listener.onPanelExpansionChanged(event); - } - } - - private void captureAltAuthInterceptor() { - verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor( - mAltAuthInterceptorCaptor.capture()); - mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue(); - } - - private void captureKeyguardStateControllerCallback() { - verify(mKeyguardStateController).addCallback( - mKeyguardStateControllerCallbackCaptor.capture()); - mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); + private void captureBouncerExpansionCallback() { + verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture()); + mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt new file mode 100644 index 000000000000..7b1976811868 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor +import com.android.systemui.keyguard.domain.interactor.BouncerInteractor +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.phone.KeyguardBouncer +import com.android.systemui.statusbar.phone.KeyguardBypassController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@TestableLooper.RunWithLooper +class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() { + lateinit var keyguardBouncerRepository: KeyguardBouncerRepository + + @Before + override fun setUp() { + allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread + MockitoAnnotations.initMocks(this) + keyguardBouncerRepository = + KeyguardBouncerRepository( + mock(com.android.keyguard.ViewMediatorCallback::class.java), + mKeyguardUpdateMonitor + ) + super.setUp() + } + + override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? { + mBouncerInteractor = + BouncerInteractor( + keyguardBouncerRepository, + mock(BouncerView::class.java), + mock(Handler::class.java), + mKeyguardStateController, + mock(KeyguardSecurityModel::class.java), + mock(BouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), + mock(KeyguardBypassController::class.java), + mKeyguardUpdateMonitor + ) + return createUdfpsKeyguardViewController(/* useModernBouncer */ true) + } + + /** After migration, replaces LockIconViewControllerTest version */ + @Test + fun testShouldPauseAuthBouncerShowing() = + runBlocking(IMMEDIATE) { + // GIVEN view attached and we're on the keyguard + mController.onViewAttached() + captureStatusBarStateListeners() + sendStatusBarStateChanged(StatusBarState.KEYGUARD) + + // WHEN the bouncer expansion is VISIBLE + val job = mController.listenForBouncerExpansion(this) + keyguardBouncerRepository.setVisible(true) + keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE) + yield() + + // THEN UDFPS shouldPauseAuth == true + assertTrue(mController.shouldPauseAuth()) + + job.cancel() + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt new file mode 100644 index 000000000000..54f20db59afd --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics + +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.MotionEvent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.any +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenEver +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class UdfpsShellTest : SysuiTestCase() { + + @JvmField @Rule var rule = MockitoJUnit.rule() + + // Unit under test + private lateinit var udfpsShell: UdfpsShell + + @Mock lateinit var commandRegistry: CommandRegistry + @Mock lateinit var udfpsOverlay: UdfpsOverlay + @Mock lateinit var udfpsOverlayController: UdfpsOverlayController + + @Captor private lateinit var motionEvent: ArgumentCaptor<MotionEvent> + + private val sensorBounds = Rect() + + @Before + fun setup() { + whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds) + + udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay) + udfpsShell.udfpsOverlayController = udfpsOverlayController + } + + @Test + fun testSimFingerDown() { + udfpsShell.simFingerDown() + + verify(udfpsOverlayController, times(2)).debugOnTouch(any(), motionEvent.capture()) + + assertEquals(motionEvent.allValues[0].action, MotionEvent.ACTION_DOWN) // ACTION_MOVE + assertEquals(motionEvent.allValues[1].action, MotionEvent.ACTION_MOVE) // ACTION_MOVE + } + + @Test + fun testSimFingerUp() { + udfpsShell.simFingerUp() + + verify(udfpsOverlayController).debugOnTouch(any(), motionEvent.capture()) + + assertEquals(motionEvent.value.action, MotionEvent.ACTION_UP) + } + + @Test + fun testOnUiReady() { + udfpsShell.onUiReady() + + verify(udfpsOverlayController).debugOnUiReady(any(), any()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index 94813497cb4c..b811aab6d35f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -96,7 +96,6 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); } - @Test public void testA11yDisablesTap() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); @@ -159,4 +158,11 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { }); assertThat(mBrightLineFalsingManager.isProximityNear()).isFalse(); } + + @Test + public void testA11yAction() { + assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue(); + when(mFalsingDataProvider.isA11yAction()).thenReturn(true); + assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt new file mode 100644 index 000000000000..2c904e7e3735 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt @@ -0,0 +1,57 @@ +/* + * 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.classifier + +import android.testing.AndroidTestingRunner +import android.view.View +import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK +import android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class FalsingA11yDelegateTest : SysuiTestCase() { + @Mock lateinit var falsingCollector: FalsingCollector + @Mock lateinit var view: View + lateinit var underTest: FalsingA11yDelegate + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = FalsingA11yDelegate(falsingCollector) + } + + @Test + fun testPerformAccessibilityAction_ACTION_CLICK() { + underTest.performAccessibilityAction(view, ACTION_CLICK, null) + verify(falsingCollector).onA11yAction() + } + + @Test + fun testPerformAccessibilityAction_not_ACTION_CLICK() { + underTest.performAccessibilityAction(view, ACTION_LONG_CLICK, null) + verify(falsingCollector, never()).onA11yAction() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index fa9c41a3cbb6..442bf918ad8c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -267,4 +267,10 @@ public class FalsingCollectorImplTest extends SysuiTestCase { mFalsingCollector.onTouchEvent(up); verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class)); } + + @Test + public void testOnA11yAction() { + mFalsingCollector.onA11yAction(); + verify(mFalsingDataProvider).onA11yAction(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index 5dc607fd342b..d315c2da0703 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -310,4 +310,10 @@ public class FalsingDataProviderTest extends ClassifierTest { // an empty array. assertThat(mDataProvider.getPriorMotionEvents()).isNotNull(); } + + @Test + public void test_MotionEventComplete_A11yAction() { + mDataProvider.onA11yAction(); + assertThat(mDataProvider.isA11yAction()).isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index b7f1c1a9f001..a872e4b96cce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -19,7 +19,9 @@ package com.android.systemui.clipboardoverlay; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED; +import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -37,6 +39,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastSender; +import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.screenshot.TimeoutHandler; import org.junit.After; @@ -62,7 +65,10 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { @Mock private TimeoutHandler mTimeoutHandler; @Mock + private ClipboardOverlayUtils mClipboardUtils; + @Mock private UiEventLogger mUiEventLogger; + private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private Animator mAnimator; @@ -73,7 +79,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor; private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks; - @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -84,6 +89,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mSampleClipData = new ClipData("Test", new String[]{"text/plain"}, new ClipData.Item("Test Item")); + mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false); + mOverlayController = new ClipboardOverlayController( mContext, mClipboardOverlayView, @@ -91,6 +98,8 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { getFakeBroadcastDispatcher(), mBroadcastSender, mTimeoutHandler, + mFeatureFlags, + mClipboardUtils, mUiEventLogger); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); @@ -186,4 +195,33 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED); verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED); } + + @Test + public void test_remoteCopy_withFlagOn() { + mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); + when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true); + + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mTimeoutHandler, never()).resetTimeout(); + } + + @Test + public void test_remoteCopy_withFlagOff() { + when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true); + + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mTimeoutHandler).resetTimeout(); + } + + @Test + public void test_nonRemoteCopy() { + mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true); + when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false); + + mOverlayController.setClipData(mSampleClipData, ""); + + verify(mTimeoutHandler).resetTimeout(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java new file mode 100644 index 000000000000..09b1699d3ffc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java @@ -0,0 +1,103 @@ +/* + * 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.clipboardoverlay; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.os.PersistableBundle; +import android.testing.TestableResources; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ClipboardOverlayUtilsTest extends SysuiTestCase { + + private ClipboardOverlayUtils mClipboardUtils; + + @Before + public void setUp() { + mClipboardUtils = new ClipboardOverlayUtils(); + } + + @Test + public void test_extra_withPackage_returnsTrue() { + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true); + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), b); + TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity"); + + assertTrue(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote")); + } + + @Test + public void test_noExtra_returnsFalse() { + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), null); + TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity"); + + assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote")); + } + + @Test + public void test_falseExtra_returnsFalse() { + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, false); + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), b); + TestableResources res = mContext.getOrCreateTestableResources(); + res.addOverride( + R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity"); + + assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote")); + } + + @Test + public void test_wrongPackage_returnsFalse() { + PersistableBundle b = new PersistableBundle(); + b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true); + ClipData data = constructClipData( + new String[]{"text/plain"}, new ClipData.Item("6175550000"), b); + + assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "")); + } + + static ClipData constructClipData(String[] mimeTypes, ClipData.Item item, + PersistableBundle extras) { + ClipDescription description = new ClipDescription("Test", mimeTypes); + if (extras != null) { + description.setExtras(extras); + } + return new ClipData(description, item); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt new file mode 100644 index 000000000000..49c7442b9708 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.ui + +import android.content.ComponentName +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.controls.ControlsMetricsLogger +import com.android.systemui.controls.CustomIconCache +import com.android.systemui.controls.controller.ControlsController +import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.settings.UserFileManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.ShadeController +import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.FakeSharedPreferences +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import dagger.Lazy +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString +import org.mockito.Mockito.mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class ControlsUiControllerImplTest : SysuiTestCase() { + @Mock lateinit var controlsController: ControlsController + @Mock lateinit var controlsListingController: ControlsListingController + @Mock lateinit var controlActionCoordinator: ControlActionCoordinator + @Mock lateinit var activityStarter: ActivityStarter + @Mock lateinit var shadeController: ShadeController + @Mock lateinit var iconCache: CustomIconCache + @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger + @Mock lateinit var keyguardStateController: KeyguardStateController + @Mock lateinit var userFileManager: UserFileManager + @Mock lateinit var userTracker: UserTracker + val sharedPreferences = FakeSharedPreferences() + + var uiExecutor = FakeExecutor(FakeSystemClock()) + var bgExecutor = FakeExecutor(FakeSystemClock()) + lateinit var underTest: ControlsUiControllerImpl + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + underTest = + ControlsUiControllerImpl( + Lazy { controlsController }, + context, + uiExecutor, + bgExecutor, + Lazy { controlsListingController }, + controlActionCoordinator, + activityStarter, + shadeController, + iconCache, + controlsMetricsLogger, + keyguardStateController, + userFileManager, + userTracker + ) + `when`( + userFileManager.getSharedPreferences( + DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, + 0, + 0 + ) + ) + .thenReturn(sharedPreferences) + `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())) + .thenReturn(sharedPreferences) + `when`(userTracker.userId).thenReturn(0) + } + + @Test + fun testGetPreferredStructure() { + val structureInfo = mock(StructureInfo::class.java) + underTest.getPreferredStructure(listOf(structureInfo)) + verify(userFileManager, times(2)) + .getSharedPreferences( + fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, + mode = 0, + userId = 0 + ) + } + + @Test + fun testGetPreferredStructure_differentUserId() { + val structureInfo = + listOf( + StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()), + StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()), + ) + sharedPreferences + .edit() + .putString("controls_component", structureInfo[0].componentName.flattenToString()) + .putString("controls_structure", structureInfo[0].structure.toString()) + .commit() + + val differentSharedPreferences = FakeSharedPreferences() + differentSharedPreferences + .edit() + .putString("controls_component", structureInfo[1].componentName.flattenToString()) + .putString("controls_structure", structureInfo[1].structure.toString()) + .commit() + + val previousPreferredStructure = underTest.getPreferredStructure(structureInfo) + + `when`( + userFileManager.getSharedPreferences( + DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, + 0, + 1 + ) + ) + .thenReturn(differentSharedPreferences) + `when`(userTracker.userId).thenReturn(1) + + val currentPreferredStructure = underTest.getPreferredStructure(structureInfo) + + assertThat(previousPreferredStructure).isEqualTo(structureInfo[0]) + assertThat(currentPreferredStructure).isEqualTo(structureInfo[1]) + assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 7a1568098e4a..b9ab9d37a805 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -20,6 +20,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Position import com.android.systemui.doze.DozeHost +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.argumentCaptor @@ -43,6 +45,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle private lateinit var underTest: KeyguardRepositoryImpl @@ -55,6 +58,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { statusBarStateController, keyguardStateController, dozeHost, + wakefulnessLifecycle, ) } @@ -184,4 +188,33 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() verify(statusBarStateController).removeCallback(captor.value) } + + @Test + fun wakefullness() = runBlockingTest { + val values = mutableListOf<WakefulnessModel>() + val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) + + val captor = argumentCaptor<WakefulnessLifecycle.Observer>() + verify(wakefulnessLifecycle).addObserver(captor.capture()) + + captor.value.onStartedWakingUp() + captor.value.onFinishedWakingUp() + captor.value.onStartedGoingToSleep() + captor.value.onFinishedGoingToSleep() + + assertThat(values) + .isEqualTo( + listOf( + // Initial value will be ASLEEP + WakefulnessModel.ASLEEP, + WakefulnessModel.STARTING_TO_WAKE, + WakefulnessModel.AWAKE, + WakefulnessModel.STARTING_TO_SLEEP, + WakefulnessModel.ASLEEP, + ) + ) + + job.cancel() + verify(wakefulnessLifecycle).removeObserver(captor.value) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 1b34100b1cef..64913c7c28c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -63,7 +63,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { @Before fun setUp() { - underTest = KeyguardTransitionRepository() + underTest = KeyguardTransitionRepositoryImpl() wtfHandler = WtfHandler() oldWtfHandler = Log.setWtfHandler(wtfHandler) } @@ -174,9 +174,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { } private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) { - // + 2 accounts for start and finish of automated transition - assertThat(steps.size).isEqualTo(fractions.size + 2) - assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED)) fractions.forEachIndexed { index, fraction -> assertThat(steps[index + 1]) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt index e6c8dd87d982..5743b2f03d3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt @@ -27,8 +27,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository -import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import com.android.systemui.plugins.ActivityStarter @@ -57,6 +57,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var repository: KeyguardBouncerRepository @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView + @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor @@ -86,6 +87,7 @@ class BouncerInteractorTest : SysuiTestCase() { ) `when`(repository.startingDisappearAnimation.value).thenReturn(null) `when`(repository.show.value).thenReturn(null) + `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate) } @Test @@ -97,7 +99,7 @@ class BouncerInteractorTest : SysuiTestCase() { verify(repository).setHide(false) verify(repository).setStartingToHide(false) verify(repository).setScrimmed(true) - verify(repository).setExpansion(EXPANSION_VISIBLE) + verify(repository).setPanelExpansion(EXPANSION_VISIBLE) verify(repository).setShowingSoon(true) verify(keyguardStateController).notifyBouncerShowing(true) verify(bouncerCallbackInteractor).dispatchStartingToShow() @@ -108,7 +110,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun testShow_isNotScrimmed() { - verify(repository, never()).setExpansion(EXPANSION_VISIBLE) + verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE) } @Test @@ -124,7 +126,6 @@ class BouncerInteractorTest : SysuiTestCase() { verify(falsingCollector).onBouncerHidden() verify(keyguardStateController).notifyBouncerShowing(false) verify(repository).setShowingSoon(false) - verify(repository).setOnDismissAction(null) verify(repository).setVisible(false) verify(repository).setHide(true) verify(repository).setShow(null) @@ -132,26 +133,26 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion() { - `when`(repository.expansionAmount.value).thenReturn(0.5f) - bouncerInteractor.setExpansion(0.6f) - verify(repository).setExpansion(0.6f) + `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) + bouncerInteractor.setPanelExpansion(0.6f) + verify(repository).setPanelExpansion(0.6f) verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f) } @Test fun testExpansion_fullyShown() { - `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) `when`(repository.startingDisappearAnimation.value).thenReturn(null) - bouncerInteractor.setExpansion(EXPANSION_VISIBLE) + bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE) verify(falsingCollector).onBouncerShown() verify(bouncerCallbackInteractor).dispatchFullyShown() } @Test fun testExpansion_fullyHidden() { - `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) `when`(repository.startingDisappearAnimation.value).thenReturn(null) - bouncerInteractor.setExpansion(EXPANSION_HIDDEN) + bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN) verify(repository).setVisible(false) verify(repository).setShow(null) verify(falsingCollector).onBouncerHidden() @@ -161,8 +162,8 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun testExpansion_startingToHide() { - `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE) - bouncerInteractor.setExpansion(0.1f) + `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) + bouncerInteractor.setPanelExpansion(0.1f) verify(repository).setStartingToHide(true) verify(bouncerCallbackInteractor).dispatchStartingToHide() } @@ -178,8 +179,7 @@ class BouncerInteractorTest : SysuiTestCase() { val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java) val cancelAction = mock(Runnable::class.java) bouncerInteractor.setDismissAction(onDismissAction, cancelAction) - verify(repository) - .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction)) + verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction) } @Test @@ -234,7 +234,7 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun testIsFullShowing() { `when`(repository.isVisible.value).thenReturn(true) - `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE) + `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE) `when`(repository.startingDisappearAnimation.value).thenReturn(null) assertThat(bouncerInteractor.isFullyShowing()).isTrue() `when`(repository.isVisible.value).thenReturn(false) @@ -255,7 +255,7 @@ class BouncerInteractorTest : SysuiTestCase() { assertThat(bouncerInteractor.isInTransit()).isTrue() `when`(repository.showingSoon.value).thenReturn(false) assertThat(bouncerInteractor.isInTransit()).isFalse() - `when`(repository.expansionAmount.value).thenReturn(0.5f) + `when`(repository.panelExpansionAmount.value).thenReturn(0.5f) assertThat(bouncerInteractor.isInTransit()).isTrue() } @@ -269,10 +269,9 @@ class BouncerInteractorTest : SysuiTestCase() { @Test fun testWillDismissWithAction() { - `when`(repository.onDismissAction.value?.onDismissAction) - .thenReturn(mock(ActivityStarter.OnDismissAction::class.java)) + `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true) assertThat(bouncerInteractor.willDismissWithAction()).isTrue() - `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null) + `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false) assertThat(bouncerInteractor.willDismissWithAction()).isFalse() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt new file mode 100644 index 000000000000..0424c28a1c24 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.domain.interactor + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardTransitionInteractorTest : SysuiTestCase() { + + private lateinit var underTest: KeyguardTransitionInteractor + private lateinit var repository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + repository = FakeKeyguardTransitionRepository() + underTest = KeyguardTransitionInteractor(repository) + } + + @Test + fun `transition collectors receives only appropriate events`() = + runBlocking(IMMEDIATE) { + var goneToAodSteps = mutableListOf<TransitionStep>() + val job1 = + underTest.goneToAodTransition.onEach { goneToAodSteps.add(it) }.launchIn(this) + + var aodToLockscreenSteps = mutableListOf<TransitionStep>() + val job2 = + underTest.aodToLockscreenTransition + .onEach { aodToLockscreenSteps.add(it) } + .launchIn(this) + + val steps = mutableListOf<TransitionStep>() + steps.add(TransitionStep(AOD, GONE, 0f, STARTED)) + steps.add(TransitionStep(AOD, GONE, 1f, FINISHED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(GONE, AOD, 0f, STARTED)) + steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING)) + steps.add(TransitionStep(GONE, AOD, 0.2f, RUNNING)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5)) + assertThat(goneToAodSteps).isEqualTo(steps.subList(5, 8)) + + job1.cancel() + job2.cancel() + } + + @Test + fun dozeAmountTransitionTest() = + runBlocking(IMMEDIATE) { + var dozeAmountSteps = mutableListOf<TransitionStep>() + val job = + underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this) + + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(dozeAmountSteps.subList(0, 3)) + .isEqualTo( + listOf( + steps[0].copy(value = 1f - steps[0].value), + steps[1].copy(value = 1f - steps[1].value), + steps[2].copy(value = 1f - steps[2].value), + ) + ) + assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7)) + + job.cancel() + } + + @Test + fun keyguardStateTests() = + runBlocking(IMMEDIATE) { + var finishedSteps = mutableListOf<KeyguardState>() + val job1 = + underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this) + var startedSteps = mutableListOf<KeyguardState>() + val job2 = underTest.startedKeyguardState.onEach { startedSteps.add(it) }.launchIn(this) + + val steps = mutableListOf<TransitionStep>() + + steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING)) + steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING)) + steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED)) + steps.add(TransitionStep(AOD, GONE, 1f, STARTED)) + + steps.forEach { repository.sendTransitionStep(it) } + + assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD)) + assertThat(startedSteps).isEqualTo(listOf(LOCKSCREEN, AOD, GONE)) + + job1.cancel() + job2.cancel() + } + + companion object { + private val IMMEDIATE = Dispatchers.Main.immediate + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt index 5bb74e5a31f1..a8f413848009 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaViewHolder import com.android.systemui.monet.ColorScheme +import com.android.systemui.ripple.MultiRippleController import junit.framework.Assert.assertEquals import org.junit.After import org.junit.Before @@ -60,6 +61,7 @@ class ColorSchemeTransitionTest : SysuiTestCase() { private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory @Mock private lateinit var mediaViewHolder: MediaViewHolder @Mock private lateinit var gutsViewHolder: GutsViewHolder + @Mock private lateinit var multiRippleController: MultiRippleController @JvmField @Rule val mockitoRule = MockitoJUnit.rule() @@ -70,7 +72,12 @@ class ColorSchemeTransitionTest : SysuiTestCase() { whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR) colorSchemeTransition = - ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory) + ColorSchemeTransition( + context, + mediaViewHolder, + multiRippleController, + animatingColorTransitionFactory + ) colorTransition = object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) { 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 584305334b6f..81901569bde8 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 @@ -59,6 +59,8 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.bluetooth.BroadcastDialogController import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.GutsViewHolder import com.android.systemui.media.controls.models.player.MediaAction @@ -76,6 +78,7 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.dialog.MediaOutputDialogFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager +import com.android.systemui.ripple.MultiRippleView import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.animation.TransitionLayout @@ -174,6 +177,7 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var cancelText: TextView private lateinit var dismiss: FrameLayout private lateinit var dismissText: TextView + private lateinit var multiRippleView: MultiRippleView private lateinit var session: MediaSession private lateinit var device: MediaDeviceData @@ -205,6 +209,8 @@ public class MediaControlPanelTest : SysuiTestCase() { private lateinit var recSubtitle2: TextView private lateinit var recSubtitle3: TextView private var shouldShowBroadcastButton: Boolean = false + private val fakeFeatureFlag = + FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) } @JvmField @Rule val mockito = MockitoJUnit.rule() @@ -244,7 +250,8 @@ public class MediaControlPanelTest : SysuiTestCase() { keyguardStateController, activityIntentHelper, lockscreenUserManager, - broadcastDialogController + broadcastDialogController, + fakeFeatureFlag ) { override fun loadAnimator( animId: Int, @@ -374,6 +381,8 @@ public class MediaControlPanelTest : SysuiTestCase() { ) } + multiRippleView = MultiRippleView(context, null) + whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) whenever(viewHolder.albumView).thenReturn(albumView) @@ -414,6 +423,8 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4) whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier) + + whenever(viewHolder.multiRippleView).thenReturn(multiRippleView) } /** Initialize elements for the recommendation view holder */ @@ -1973,6 +1984,50 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE) } + @Test + fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() { + fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true) + val semanticActions = + MediaButton( + playOrPause = + MediaAction( + icon = null, + action = {}, + contentDescription = "play", + background = null + ) + ) + val data = mediaData.copy(semanticActions = semanticActions) + player.attachPlayer(viewHolder) + player.bindPlayer(data, KEY) + + viewHolder.actionPlayPause.callOnClick() + + assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(1) + } + + @Test + fun onButtonClick_touchRippleFlagDisabled_doesNotPlayTouchRipple() { + fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, false) + val semanticActions = + MediaButton( + playOrPause = + MediaAction( + icon = null, + action = {}, + contentDescription = "play", + background = null + ) + ) + val data = mediaData.copy(semanticActions = semanticActions) + player.attachPlayer(viewHolder) + player.bindPlayer(data, KEY) + + viewHolder.actionPlayPause.callOnClick() + + assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(0) + } + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = withArgCaptor { verify(seekBarViewModel).setScrubbingChangeListener(capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 6adce7a827b6..c1fa9b39f50b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -61,6 +61,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.MotionEvent; import android.view.View; +import android.view.ViewRootImpl; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; @@ -201,6 +202,8 @@ public class NavigationBarTest extends SysuiTestCase { private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private Resources mResources; + @Mock + private ViewRootImpl mViewRootImpl; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); @@ -227,6 +230,7 @@ public class NavigationBarTest extends SysuiTestCase { when(mUserContextProvider.createCurrentUserContext(any(Context.class))) .thenReturn(mContext); when(mNavigationBarView.getResources()).thenReturn(mResources); + when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl); setupSysuiDependency(); // This class inflates views that call Dependency.get, thus these injections are still // necessary. diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java index 99a17a613041..9115ab3bacca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; +import android.widget.TextView; import androidx.test.filters.SmallTest; @@ -48,6 +49,7 @@ public class QSCarrierTest extends SysuiTestCase { public void setUp() throws Exception { mTestableLooper = TestableLooper.get(this); LayoutInflater inflater = LayoutInflater.from(mContext); + mContext.ensureTestableResources(); mTestableLooper.runWithLooper(() -> mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null)); @@ -119,4 +121,30 @@ public class QSCarrierTest extends SysuiTestCase { mQSCarrier.updateState(c, true); assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility()); } + + @Test + public void testCarrierNameMaxWidth_smallScreen_fromResource() { + int maxEms = 10; + mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms); + mContext.getOrCreateTestableResources() + .addOverride(R.bool.config_use_large_screen_shade_header, false); + TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text); + + mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration()); + + assertEquals(maxEms, carrierText.getMaxEms()); + } + + @Test + public void testCarrierNameMaxWidth_largeScreen_maxInt() { + int maxEms = 10; + mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms); + mContext.getOrCreateTestableResources() + .addOverride(R.bool.config_use_large_screen_shade_header, true); + TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text); + + mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration()); + + assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java index 2c76be64aa7c..b067ee76f3b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.service.quicksettings.Tile; import android.testing.AndroidTestingRunner; @@ -136,6 +137,20 @@ public class QSIconViewImplTest extends SysuiTestCase { assertEquals(mIconView.getColor(s1), mIconView.getColor(s2)); } + @Test + public void testIconNotAnimatedWhenAllowAnimationsFalse() { + ImageView iv = new ImageView(mContext); + AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class); + State s = new State(); + s.icon = mock(Icon.class); + when(s.icon.getDrawable(any())).thenReturn(d); + when(s.icon.getInvisibleDrawable(any())).thenReturn(d); + + mIconView.updateIcon(iv, s, false); + + verify(d, never()).start(); + } + private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) { return new Drawable.ConstantState() { @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt new file mode 100644 index 000000000000..05512e5bf1ce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.ripple + +import android.graphics.Color +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MultiRippleControllerTest : SysuiTestCase() { + private lateinit var multiRippleController: MultiRippleController + private lateinit var multiRippleView: MultiRippleView + private lateinit var rippleAnimationConfig: RippleAnimationConfig + private val fakeSystemClock = FakeSystemClock() + + // FakeExecutor is needed to run animator. + private val fakeExecutor = FakeExecutor(fakeSystemClock) + + @Before + fun setup() { + rippleAnimationConfig = RippleAnimationConfig(duration = 1000L) + multiRippleView = MultiRippleView(context, null) + multiRippleController = MultiRippleController(multiRippleView) + } + + @Test + fun updateColor_updatesColor() { + val initialColor = Color.WHITE + val expectedColor = Color.RED + + fakeExecutor.execute { + val rippleAnimation = + RippleAnimation(rippleAnimationConfig.apply { this.color = initialColor }) + + with(multiRippleController) { + play(rippleAnimation) + updateColor(expectedColor) + } + + assertThat(rippleAnimationConfig.color).isEqualTo(expectedColor) + } + } + + @Test + fun play_playsRipple() { + fakeExecutor.execute { + val rippleAnimation = RippleAnimation(rippleAnimationConfig) + + multiRippleController.play(rippleAnimation) + + assertThat(multiRippleView.ripples.size).isEqualTo(1) + assertThat(multiRippleView.ripples[0]).isEqualTo(rippleAnimation) + } + } + + @Test + fun play_doesNotExceedMaxRipple() { + fakeExecutor.execute { + for (i in 0..MAX_RIPPLE_NUMBER + 10) { + multiRippleController.play(RippleAnimation(rippleAnimationConfig)) + } + + assertThat(multiRippleView.ripples.size).isEqualTo(MAX_RIPPLE_NUMBER) + } + } + + @Test + fun play_onEnd_removesAnimation() { + fakeExecutor.execute { + val rippleAnimation = RippleAnimation(rippleAnimationConfig) + multiRippleController.play(rippleAnimation) + + assertThat(multiRippleView.ripples.size).isEqualTo(1) + assertThat(multiRippleView.ripples[0]).isEqualTo(rippleAnimation) + + fakeSystemClock.advanceTime(rippleAnimationConfig.duration) + + assertThat(multiRippleView.ripples.size).isEqualTo(0) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt new file mode 100644 index 000000000000..7662282a04f4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.ripple + +import android.graphics.Color +import android.testing.AndroidTestingRunner +import androidx.core.graphics.ColorUtils +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RippleAnimationTest : SysuiTestCase() { + + private val fakeSystemClock = FakeSystemClock() + private val fakeExecutor = FakeExecutor(fakeSystemClock) + + @Test + fun init_shaderHasCorrectConfig() { + val config = + RippleAnimationConfig( + duration = 3000L, + pixelDensity = 2f, + color = Color.RED, + opacity = 30, + shouldFillRipple = true, + sparkleStrength = 0.3f + ) + val rippleAnimation = RippleAnimation(config) + + with(rippleAnimation.rippleShader) { + assertThat(rippleFill).isEqualTo(config.shouldFillRipple) + assertThat(pixelDensity).isEqualTo(config.pixelDensity) + assertThat(color).isEqualTo(ColorUtils.setAlphaComponent(config.color, config.opacity)) + assertThat(sparkleStrength).isEqualTo(config.sparkleStrength) + } + } + + @Test + fun updateColor_updatesColorCorrectly() { + val initialColor = Color.WHITE + val expectedColor = Color.RED + val config = RippleAnimationConfig(color = initialColor) + val rippleAnimation = RippleAnimation(config) + + fakeExecutor.execute { + with(rippleAnimation) { + play() + updateColor(expectedColor) + } + + assertThat(config.color).isEqualTo(expectedColor) + } + } + + @Test + fun play_updatesIsPlaying() { + val config = RippleAnimationConfig(duration = 1000L) + val rippleAnimation = RippleAnimation(config) + + fakeExecutor.execute { + rippleAnimation.play() + + assertThat(rippleAnimation.isPlaying()).isTrue() + + // move time to finish the animation + fakeSystemClock.advanceTime(config.duration) + + assertThat(rippleAnimation.isPlaying()).isFalse() + } + } + + @Test + fun play_onEnd_triggersOnAnimationEnd() { + val config = RippleAnimationConfig(duration = 1000L) + val rippleAnimation = RippleAnimation(config) + var animationEnd = false + + fakeExecutor.execute { + rippleAnimation.play(onAnimationEnd = { animationEnd = true }) + + fakeSystemClock.advanceTime(config.duration) + + assertThat(animationEnd).isTrue() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java index 4c44dacab1a2..f4bc232702e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import static java.nio.charset.StandardCharsets.US_ASCII; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.graphics.Bitmap; @@ -31,9 +32,11 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.net.Uri; import android.os.Build; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.UserHandle; import android.provider.MediaStore; import android.testing.AndroidTestingRunner; @@ -41,11 +44,18 @@ import androidx.exifinterface.media.ExifInterface; import androidx.test.filters.MediumTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.flags.FakeFeatureFlags; +import com.android.systemui.flags.Flags; import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -60,7 +70,6 @@ import java.util.concurrent.Executor; @RunWith(AndroidTestingRunner.class) @MediumTest // file I/O public class ImageExporterTest extends SysuiTestCase { - /** Executes directly in the caller's thread */ private static final Executor DIRECT_EXECUTOR = Runnable::run; private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII); @@ -68,6 +77,15 @@ public class ImageExporterTest extends SysuiTestCase { private static final ZonedDateTime CAPTURE_TIME = ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST")); + private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + @Mock + private ContentResolver mMockContentResolver; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + @Test public void testImageFilename() { assertEquals("image file name", "Screenshot_20201215-131500.png", @@ -92,7 +110,8 @@ public class ImageExporterTest extends SysuiTestCase { @Test public void testImageExport() throws ExecutionException, InterruptedException, IOException { ContentResolver contentResolver = mContext.getContentResolver(); - ImageExporter exporter = new ImageExporter(contentResolver); + mFeatureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true); + ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags); UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"); Bitmap original = createCheckerBitmap(10, 10, 10); @@ -168,6 +187,44 @@ public class ImageExporterTest extends SysuiTestCase { values.getAsLong(MediaStore.MediaColumns.DATE_EXPIRES)); } + @Test + public void testSetUser() { + mFeatureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true); + ImageExporter exporter = new ImageExporter(mMockContentResolver, mFeatureFlags); + + UserHandle imageUserHande = UserHandle.of(10); + + ArgumentCaptor<Uri> uriCaptor = ArgumentCaptor.forClass(Uri.class); + // Capture the URI and then return null to bail out of export. + Mockito.when(mMockContentResolver.insert(uriCaptor.capture(), Mockito.any())).thenReturn( + null); + exporter.export(DIRECT_EXECUTOR, UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"), + null, CAPTURE_TIME, imageUserHande); + + Uri expected = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + expected = ContentProvider.maybeAddUserId(expected, imageUserHande.getIdentifier()); + + assertEquals(expected, uriCaptor.getValue()); + } + + @Test + public void testSetUser_noWorkProfile() { + mFeatureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false); + ImageExporter exporter = new ImageExporter(mMockContentResolver, mFeatureFlags); + + UserHandle imageUserHandle = UserHandle.of(10); + + ArgumentCaptor<Uri> uriCaptor = ArgumentCaptor.forClass(Uri.class); + // Capture the URI and then return null to bail out of export. + Mockito.when(mMockContentResolver.insert(uriCaptor.capture(), Mockito.any())).thenReturn( + null); + exporter.export(DIRECT_EXECUTOR, UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"), + null, CAPTURE_TIME, imageUserHandle); + + // The user handle should be ignored here since the flag is off. + assertEquals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, uriCaptor.getValue()); + } + @SuppressWarnings("SameParameterValue") private Bitmap createCheckerBitmap(int tileSize, int w, int h) { Bitmap bitmap = Bitmap.createBitmap(w * tileSize, h * tileSize, Bitmap.Config.ARGB_8888); diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt index 3a4da86b8045..fa1fedb7c119 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt @@ -62,7 +62,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever private const val USER_ID = 1 -private const val TASK_ID = 1 +private const val TASK_ID = 11 @RunWith(AndroidTestingRunner::class) @SmallTest 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 ac4dd49208c2..c98c1f2bc97e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -129,7 +129,6 @@ import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; @@ -153,7 +152,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; -import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -199,7 +197,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private KeyguardBottomAreaView mKeyguardBottomArea; @Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController; @Mock private KeyguardBottomAreaView mQsFrame; - @Mock private NotificationIconAreaController mNotificationAreaController; @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private NotificationShelfController mNotificationShelfController; @Mock private KeyguardStatusBarView mKeyguardStatusBar; @@ -227,7 +224,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private Resources mResources; @Mock private Configuration mConfiguration; @Mock private KeyguardClockSwitch mKeyguardClockSwitch; - @Mock private MediaHierarchyManager mMediaHiearchyManager; + @Mock private MediaHierarchyManager mMediaHierarchyManager; @Mock private ConversationNotificationManager mConversationNotificationManager; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory; @@ -254,7 +251,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; @Mock private LockIconViewController mLockIconViewController; @Mock private KeyguardMediaController mKeyguardMediaController; - @Mock private PrivacyDotViewController mPrivacyDotViewController; @Mock private NavigationModeController mNavigationModeController; @Mock private NavigationBarController mNavigationBarController; @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController; @@ -294,7 +290,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { private ConfigurationController mConfigurationController; private SysuiStatusBarStateController mStatusBarStateController; private NotificationPanelViewController mNotificationPanelViewController; - private View.AccessibilityDelegate mAccessibiltyDelegate; + private View.AccessibilityDelegate mAccessibilityDelegate; private NotificationsQuickSettingsContainer mNotificationContainerParent; private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners; private Handler mMainHandler; @@ -456,7 +452,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mShadeLog, mConfigurationController, () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager, - mConversationNotificationManager, mMediaHiearchyManager, + mConversationNotificationManager, mMediaHierarchyManager, mStatusBarKeyguardViewManager, mNotificationsQSContainerController, mNotificationStackScrollLayoutController, @@ -465,7 +461,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mKeyguardUserSwitcherComponentFactory, mKeyguardStatusBarViewComponentFactory, mLockscreenShadeTransitionController, - mNotificationAreaController, mAuthController, mScrimController, mUserManager, @@ -474,7 +469,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mAmbientState, mLockIconViewController, mKeyguardMediaController, - mPrivacyDotViewController, mTapAgainViewController, mNavigationModeController, mNavigationBarController, @@ -492,6 +486,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mSysUiState, () -> mKeyguardBottomAreaViewController, mKeyguardUnlockAnimationController, + mKeyguardIndicationController, mNotificationListContainer, mNotificationStackSizeCalculator, mUnlockedScreenOffAnimationController, @@ -505,8 +500,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { () -> {}, mNotificationShelfController); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - mNotificationPanelViewController.setKeyguardIndicationController( - mKeyguardIndicationController); ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); verify(mView, atLeast(1)).addOnAttachStateChangeListener( @@ -516,9 +509,9 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor = ArgumentCaptor.forClass(View.AccessibilityDelegate.class); verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture()); - mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue(); + mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue(); mNotificationPanelViewController.getStatusBarStateController() - .addCallback(mNotificationPanelViewController.mStatusBarStateListener); + .addCallback(mNotificationPanelViewController.getStatusBarStateListener()); mNotificationPanelViewController .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class)); verify(mNotificationStackScrollLayoutController) @@ -773,8 +766,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */, 0 /* metaState */)); - assertThat(mNotificationPanelViewController.getClosing()).isTrue(); - assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue(); + assertThat(mNotificationPanelViewController.isClosing()).isTrue(); + assertThat(mNotificationPanelViewController.isFlinging()).isTrue(); // simulate touch that does not exceed touch slop onTouchEvent(MotionEvent.obtain(2L /* downTime */, @@ -788,8 +781,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { 0 /* metaState */)); // fling should still be called after a touch that does not exceed touch slop - assertThat(mNotificationPanelViewController.getClosing()).isTrue(); - assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue(); + assertThat(mNotificationPanelViewController.isClosing()).isTrue(); + assertThat(mNotificationPanelViewController.isFlinging()).isTrue(); } @Test @@ -844,7 +837,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testA11y_initializeNode() { AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo(); - mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); + mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo); List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList(); assertThat(actionList).containsAtLeastElementsIn( @@ -856,7 +849,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testA11y_scrollForward() { - mAccessibiltyDelegate.performAccessibilityAction( + mAccessibilityDelegate.performAccessibilityAction( mView, AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(), null); @@ -866,7 +859,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void testA11y_scrollUp() { - mAccessibiltyDelegate.performAccessibilityAction( + mAccessibilityDelegate.performAccessibilityAction( mView, AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(), null); @@ -1282,7 +1275,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mNotificationPanelViewController.expandWithQs(); verify(mLockscreenShadeTransitionController).goToLockedShade( - /* expandedView= */null, /* needsQSAnimation= */false); + /* expandedView= */null, /* needsQSAnimation= */true); } @Test @@ -1329,11 +1322,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() { enableSplitShade(/* enabled= */ true); mShadeExpansionStateManager.updateState(STATE_CLOSED); - assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); + assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); mShadeExpansionStateManager.updateState(STATE_OPENING); - assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue(); + assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isTrue(); } @Test @@ -1345,18 +1338,18 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { // going to lockscreen would trigger STATE_OPENING mShadeExpansionStateManager.updateState(STATE_OPENING); - assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); + assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); } @Test public void testQsImmediateResetsWhenPanelOpensOrCloses() { - mNotificationPanelViewController.mQsExpandImmediate = true; + mNotificationPanelViewController.setQsExpandImmediate(true); mShadeExpansionStateManager.updateState(STATE_OPEN); - assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); + assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); - mNotificationPanelViewController.mQsExpandImmediate = true; + mNotificationPanelViewController.setQsExpandImmediate(true); mShadeExpansionStateManager.updateState(STATE_CLOSED); - assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse(); + assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse(); } @Test @@ -1399,7 +1392,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() { - mNotificationPanelViewController.mQs = mQs; + mNotificationPanelViewController.setQs(mQs); when(mQsFrame.getX()).thenReturn(0f); when(mQsFrame.getWidth()).thenReturn(1000); when(mQsHeader.getTop()).thenReturn(0); @@ -1419,7 +1412,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() { enableSplitShade(true); - mNotificationPanelViewController.mQs = mQs; + mNotificationPanelViewController.setQs(mQs); when(mQsFrame.getX()).thenReturn(0f); when(mQsFrame.getWidth()).thenReturn(1000); when(mQsHeader.getTop()).thenReturn(0); @@ -1495,7 +1488,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() { - mNotificationPanelViewController.mQs = mQs; + mNotificationPanelViewController.setQs(mQs); setIsFullWidth(true); @@ -1504,7 +1497,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() { - mNotificationPanelViewController.mQs = mQs; + mNotificationPanelViewController.setQs(mQs); setIsFullWidth(false); @@ -1513,7 +1506,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onLayoutChange_qsNotSet_doesNotCrash() { - mNotificationPanelViewController.mQs = null; + mNotificationPanelViewController.setQs(null); triggerLayoutChange(); } @@ -1539,7 +1532,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() { float squishinessFraction = 0.456f; - mNotificationPanelViewController.mQs = mQs; + mNotificationPanelViewController.setQs(mQs); when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) .thenReturn(squishinessFraction); when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) @@ -1552,7 +1545,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { /* delay= */ 0 ); - mNotificationPanelViewController.setQsExpansion(/* height= */ 123); + mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123); // First for setTransitionToFullShadeAmount and then setQsExpansion verify(mQs, times(2)).setQsExpansion( @@ -1567,13 +1560,13 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() { float lsSquishinessFraction = 0.456f; float nsslSquishinessFraction = 0.987f; - mNotificationPanelViewController.mQs = mQs; + mNotificationPanelViewController.setQs(mQs); when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction()) .thenReturn(lsSquishinessFraction); when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction()) .thenReturn(nsslSquishinessFraction); - mNotificationPanelViewController.setQsExpansion(/* height= */ 123); + mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123); verify(mQs).setQsExpansion( /* expansion= */ anyFloat(), @@ -1586,7 +1579,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.mStatusBarStateListener; + mNotificationPanelViewController.getStatusBarStateListener(); statusBarStateListener.onStateChanged(KEYGUARD); mNotificationPanelViewController.setDozing(false, false); @@ -1601,7 +1594,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() { StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.mStatusBarStateListener; + mNotificationPanelViewController.getStatusBarStateListener(); statusBarStateListener.onStateChanged(KEYGUARD); mNotificationPanelViewController.setDozing(false, false); when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false); @@ -1616,7 +1609,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() { StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.mStatusBarStateListener; + mNotificationPanelViewController.getStatusBarStateListener(); statusBarStateListener.onStateChanged(KEYGUARD); mNotificationPanelViewController.setDozing(false, false); when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true); @@ -1631,7 +1624,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.mStatusBarStateListener; + mNotificationPanelViewController.getStatusBarStateListener(); statusBarStateListener.onStateChanged(KEYGUARD); mNotificationPanelViewController.setDozing(true, false); @@ -1645,7 +1638,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { @Test public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() { StatusBarStateController.StateListener statusBarStateListener = - mNotificationPanelViewController.mStatusBarStateListener; + mNotificationPanelViewController.getStatusBarStateListener(); statusBarStateListener.onStateChanged(SHADE_LOCKED); mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); @@ -1664,11 +1657,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() { // Given: Shade is expanded mNotificationPanelViewController.notifyExpandingFinished(); - mNotificationPanelViewController.setIsClosing(false); + mNotificationPanelViewController.setClosing(false); // When: Shade flings to close not canceled mNotificationPanelViewController.notifyExpandingStarted(); - mNotificationPanelViewController.setIsClosing(true); + mNotificationPanelViewController.setClosing(true); mNotificationPanelViewController.onFlingEnd(false); // Then: AmbientState's mIsClosing should be set to false @@ -1680,6 +1673,15 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped(); } + @Test + public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() { + mStatusBarStateController.setState(SHADE); + enableSplitShade(true); + int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance(); + mNotificationPanelViewController.setExpandedHeight(transitionDistance); + assertThat(mNotificationPanelViewController.isFullyExpanded()).isTrue(); + } + private static MotionEvent createMotionEvent(int x, int y, int action) { return MotionEvent.obtain( /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java index 26a0770a7bba..a4a7995ae3c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java @@ -152,7 +152,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true); + when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we should intercept touch @@ -165,7 +165,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false); + when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we shouldn't intercept touch @@ -178,7 +178,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true); + when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true); when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false); // THEN we should handle the touch diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt index 09add652483e..43c694245eba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt @@ -66,6 +66,8 @@ class PulsingGestureListenerTest : SysuiTestCase() { private lateinit var dumpManager: DumpManager @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock + private lateinit var shadeLogger: ShadeLogger private lateinit var tunableCaptor: ArgumentCaptor<Tunable> private lateinit var underTest: PulsingGestureListener @@ -81,6 +83,7 @@ class PulsingGestureListenerTest : SysuiTestCase() { centralSurfaces, ambientDisplayConfiguration, statusBarStateController, + shadeLogger, tunerService, dumpManager ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt index 09d51f6447b0..5a62cc18cae3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt @@ -21,61 +21,55 @@ import org.mockito.junit.MockitoJUnit @RunWith(AndroidTestingRunner::class) @SmallTest -class RegionSamplingInstanceTest : SysuiTestCase() { +class RegionSamplerTest : SysuiTestCase() { - @JvmField @Rule - val mockito = MockitoJUnit.rule() + @JvmField @Rule val mockito = MockitoJUnit.rule() @Mock private lateinit var sampledView: View @Mock private lateinit var mainExecutor: Executor @Mock private lateinit var bgExecutor: Executor @Mock private lateinit var regionSampler: RegionSamplingHelper - @Mock private lateinit var updateFun: RegionSamplingInstance.UpdateColorCallback @Mock private lateinit var pw: PrintWriter @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback - private lateinit var regionSamplingInstance: RegionSamplingInstance + private lateinit var mRegionSampler: RegionSampler + private var updateFun: UpdateColorCallback = {} @Before fun setUp() { whenever(sampledView.isAttachedToWindow).thenReturn(true) - whenever(regionSampler.callback).thenReturn(this@RegionSamplingInstanceTest.callback) - - regionSamplingInstance = object : RegionSamplingInstance( - sampledView, - mainExecutor, - bgExecutor, - true, - updateFun - ) { - override fun createRegionSamplingHelper( + whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback) + + mRegionSampler = + object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) { + override fun createRegionSamplingHelper( sampledView: View, callback: RegionSamplingHelper.SamplingCallback, mainExecutor: Executor?, bgExecutor: Executor? - ): RegionSamplingHelper { - return this@RegionSamplingInstanceTest.regionSampler + ): RegionSamplingHelper { + return this@RegionSamplerTest.regionSampler + } } - } } @Test fun testStartRegionSampler() { - regionSamplingInstance.startRegionSampler() + mRegionSampler.startRegionSampler() verify(regionSampler).start(Rect(0, 0, 0, 0)) } @Test fun testStopRegionSampler() { - regionSamplingInstance.stopRegionSampler() + mRegionSampler.stopRegionSampler() verify(regionSampler).stop() } @Test fun testDump() { - regionSamplingInstance.dump(pw) + mRegionSampler.dump(pw) verify(regionSampler).dump(pw) } @@ -91,23 +85,18 @@ class RegionSamplingInstanceTest : SysuiTestCase() { @Test fun testFlagFalse() { - regionSamplingInstance = object : RegionSamplingInstance( - sampledView, - mainExecutor, - bgExecutor, - false, - updateFun - ) { - override fun createRegionSamplingHelper( + mRegionSampler = + object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) { + override fun createRegionSamplingHelper( sampledView: View, callback: RegionSamplingHelper.SamplingCallback, mainExecutor: Executor?, bgExecutor: Executor? - ): RegionSamplingHelper { - return this@RegionSamplingInstanceTest.regionSampler + ): RegionSamplingHelper { + return this@RegionSamplerTest.regionSampler + } } - } - Assert.assertEquals(regionSamplingInstance.regionSampler, null) + Assert.assertEquals(mRegionSampler.regionSampler, null) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index cf7f8dd26647..c377990c0fa9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -33,7 +33,7 @@ import android.graphics.Rect; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.Bundle; import android.view.InsetsVisibilities; import android.view.WindowInsetsController.Appearance; @@ -492,11 +492,12 @@ public class CommandQueueTest extends SysuiTestCase { } @Test - public void testSetUdfpsHbmListener() { - final IUdfpsHbmListener listener = mock(IUdfpsHbmListener.class); - mCommandQueue.setUdfpsHbmListener(listener); + public void testSetUdfpsRefreshRateCallback() { + final IUdfpsRefreshRateRequestCallback callback = + mock(IUdfpsRefreshRateRequestCallback.class); + mCommandQueue.setUdfpsRefreshRateCallback(callback); waitForIdleSync(); - verify(mCallbacks).setUdfpsHbmListener(eq(listener)); + verify(mCallbacks).setUdfpsRefreshRateCallback(eq(callback)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index 3ff7639e9262..f96c39f007dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -406,6 +406,10 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(mGroupSummary) verify(mHeadsUpManager).showNotification(mGroupSibling1) + + // In addition make sure we have explicitly marked the summary as having interrupted due + // to the alert being transferred + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -424,6 +428,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(mGroupSummary) verify(mHeadsUpManager).showNotification(mGroupChild1) + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -449,6 +454,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(mGroupSummary) verify(mHeadsUpManager).showNotification(mGroupSibling1) verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -474,6 +480,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager, never()).showNotification(mGroupSummary) verify(mHeadsUpManager).showNotification(mGroupChild1) verify(mHeadsUpManager, never()).showNotification(mGroupChild2) + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -512,6 +519,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager).showNotification(mGroupPriority) verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -548,6 +556,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager).showNotification(mGroupPriority) verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -582,6 +591,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { verify(mHeadsUpManager).showNotification(mGroupPriority) verify(mHeadsUpManager, never()).showNotification(mGroupSibling1) verify(mHeadsUpManager, never()).showNotification(mGroupSibling2) + assertTrue(mGroupSummary.hasInterrupted()) } @Test @@ -672,6 +682,35 @@ class HeadsUpCoordinatorTest : SysuiTestCase() { } @Test + fun testNoTransfer_groupSummaryNotAlerting() { + // When we have a group where the summary should not alert and exactly one child should + // alert, we should never mark the group summary as interrupted (because it doesn't). + setShouldHeadsUp(mGroupSummary, false) + setShouldHeadsUp(mGroupChild1, true) + setShouldHeadsUp(mGroupChild2, false) + + mCollectionListener.onEntryAdded(mGroupSummary) + mCollectionListener.onEntryAdded(mGroupChild1) + mCollectionListener.onEntryAdded(mGroupChild2) + val groupEntry = GroupEntryBuilder() + .setSummary(mGroupSummary) + .setChildren(listOf(mGroupChild1, mGroupChild2)) + .build() + mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry)) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any()) + mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry)) + + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any()) + finishBind(mGroupChild1) + verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any()) + + verify(mHeadsUpManager, never()).showNotification(mGroupSummary) + verify(mHeadsUpManager).showNotification(mGroupChild1) + verify(mHeadsUpManager, never()).showNotification(mGroupChild2) + assertFalse(mGroupSummary.hasInterrupted()) + } + + @Test fun testOnRankingApplied_newEntryShouldAlert() { // GIVEN that mEntry has never interrupted in the past, and now should // and is new enough to do so diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java deleted file mode 100644 index ab712649a90f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.collection.render; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import android.content.Context; -import android.testing.AndroidTestingRunner; -import android.view.View; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.List; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -public class ShadeViewDifferTest extends SysuiTestCase { - private ShadeViewDiffer mDiffer; - - private FakeController mRootController = new FakeController(mContext, "RootController"); - private FakeController mController1 = new FakeController(mContext, "Controller1"); - private FakeController mController2 = new FakeController(mContext, "Controller2"); - private FakeController mController3 = new FakeController(mContext, "Controller3"); - private FakeController mController4 = new FakeController(mContext, "Controller4"); - private FakeController mController5 = new FakeController(mContext, "Controller5"); - private FakeController mController6 = new FakeController(mContext, "Controller6"); - private FakeController mController7 = new FakeController(mContext, "Controller7"); - - @Mock - ShadeViewDifferLogger mLogger; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mDiffer = new ShadeViewDiffer(mRootController, mLogger); - } - - @Test - public void testAddInitialViews() { - // WHEN a spec is applied to an empty root - // THEN the final tree matches the spec - applySpecAndCheck( - node(mController1), - node(mController2, - node(mController3), - node(mController4) - ), - node(mController5) - ); - } - - @Test - public void testDetachViews() { - // GIVEN a preexisting tree of controllers - applySpecAndCheck( - node(mController1), - node(mController2, - node(mController3), - node(mController4) - ), - node(mController5) - ); - - // WHEN the new spec removes nodes - // THEN the final tree matches the spec - applySpecAndCheck( - node(mController5) - ); - } - - @Test - public void testReparentChildren() { - // GIVEN a preexisting tree of controllers - applySpecAndCheck( - node(mController1), - node(mController2, - node(mController3), - node(mController4) - ), - node(mController5) - ); - - // WHEN the parents of the controllers are all shuffled around - // THEN the final tree matches the spec - applySpecAndCheck( - node(mController1), - node(mController4), - node(mController3, - node(mController2) - ) - ); - } - - @Test - public void testReorderChildren() { - // GIVEN a preexisting tree of controllers - applySpecAndCheck( - node(mController1), - node(mController2), - node(mController3), - node(mController4) - ); - - // WHEN the children change order - // THEN the final tree matches the spec - applySpecAndCheck( - node(mController3), - node(mController2), - node(mController4), - node(mController1) - ); - } - - @Test - public void testRemovedGroupsAreBrokenApart() { - // GIVEN a preexisting tree with a group - applySpecAndCheck( - node(mController1), - node(mController2, - node(mController3), - node(mController4), - node(mController5) - ) - ); - - // WHEN the new spec removes the entire group - applySpecAndCheck( - node(mController1) - ); - - // THEN the group children are no longer attached to their parent - assertNull(mController3.getView().getParent()); - assertNull(mController4.getView().getParent()); - assertNull(mController5.getView().getParent()); - } - - @Test - public void testUnmanagedViews() { - // GIVEN a preexisting tree of controllers - applySpecAndCheck( - node(mController1), - node(mController2, - node(mController3), - node(mController4) - ), - node(mController5) - ); - - // GIVEN some additional unmanaged views attached to the tree - View unmanagedView1 = new View(mContext); - View unmanagedView2 = new View(mContext); - - mRootController.getView().addView(unmanagedView1, 1); - mController2.getView().addView(unmanagedView2, 0); - - // WHEN a new spec is applied with additional nodes - // THEN the final tree matches the spec - applySpecAndCheck( - node(mController1), - node(mController2, - node(mController3), - node(mController4), - node(mController6) - ), - node(mController5), - node(mController7) - ); - - // THEN the unmanaged views have been pushed to the end of their parents - assertEquals(unmanagedView1, mRootController.view.getChildAt(4)); - assertEquals(unmanagedView2, mController2.view.getChildAt(3)); - } - - private void applySpecAndCheck(NodeSpec spec) { - mDiffer.applySpec(spec); - checkMatchesSpec(spec); - } - - private void applySpecAndCheck(SpecBuilder... children) { - applySpecAndCheck(node(mRootController, children).build()); - } - - private void checkMatchesSpec(NodeSpec spec) { - final NodeController parent = spec.getController(); - final List<NodeSpec> children = spec.getChildren(); - - for (int i = 0; i < children.size(); i++) { - NodeSpec childSpec = children.get(i); - View view = parent.getChildAt(i); - - assertEquals( - "Child " + i + " of parent " + parent.getNodeLabel() + " should be " - + childSpec.getController().getNodeLabel() + " but is instead " - + (view != null ? mDiffer.getViewLabel(view) : "null"), - view, - childSpec.getController().getView()); - - if (!childSpec.getChildren().isEmpty()) { - checkMatchesSpec(childSpec); - } - } - } - - private static class FakeController implements NodeController { - - public final FrameLayout view; - private final String mLabel; - - FakeController(Context context, String label) { - view = new FrameLayout(context); - mLabel = label; - } - - @NonNull - @Override - public String getNodeLabel() { - return mLabel; - } - - @NonNull - @Override - public FrameLayout getView() { - return view; - } - - @Override - public int getChildCount() { - return view.getChildCount(); - } - - @Override - public View getChildAt(int index) { - return view.getChildAt(index); - } - - @Override - public void addChildAt(@NonNull NodeController child, int index) { - view.addView(child.getView(), index); - } - - @Override - public void moveChildTo(@NonNull NodeController child, int index) { - view.removeView(child.getView()); - view.addView(child.getView(), index); - } - - @Override - public void removeChild(@NonNull NodeController child, boolean isTransfer) { - view.removeView(child.getView()); - } - - @Override - public void onViewAdded() { - } - - @Override - public void onViewMoved() { - } - - @Override - public void onViewRemoved() { - } - } - - private static class SpecBuilder { - private final NodeController mController; - private final SpecBuilder[] mChildren; - - SpecBuilder(NodeController controller, SpecBuilder... children) { - mController = controller; - mChildren = children; - } - - public NodeSpec build() { - return build(null); - } - - public NodeSpec build(@Nullable NodeSpec parent) { - final NodeSpecImpl spec = new NodeSpecImpl(parent, mController); - for (SpecBuilder childBuilder : mChildren) { - spec.getChildren().add(childBuilder.build(spec)); - } - return spec; - } - } - - private static SpecBuilder node(NodeController controller, SpecBuilder... children) { - return new SpecBuilder(controller, children); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt new file mode 100644 index 000000000000..15cf17dbcf86 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.statusbar.notification.collection.render + +import android.content.Context +import android.testing.AndroidTestingRunner +import android.view.View +import android.widget.FrameLayout +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ShadeViewDifferTest : SysuiTestCase() { + private lateinit var differ: ShadeViewDiffer + private val rootController = FakeController(mContext, "RootController") + private val controller1 = FakeController(mContext, "Controller1") + private val controller2 = FakeController(mContext, "Controller2") + private val controller3 = FakeController(mContext, "Controller3") + private val controller4 = FakeController(mContext, "Controller4") + private val controller5 = FakeController(mContext, "Controller5") + private val controller6 = FakeController(mContext, "Controller6") + private val controller7 = FakeController(mContext, "Controller7") + private val logger: ShadeViewDifferLogger = mock() + + @Before + fun setUp() { + differ = ShadeViewDiffer(rootController, logger) + } + + @Test + fun testAddInitialViews() { + // WHEN a spec is applied to an empty root + // THEN the final tree matches the spec + applySpecAndCheck( + node(controller1), + node(controller2, node(controller3), node(controller4)), + node(controller5) + ) + } + + @Test + fun testDetachViews() { + // GIVEN a preexisting tree of controllers + applySpecAndCheck( + node(controller1), + node(controller2, node(controller3), node(controller4)), + node(controller5) + ) + + // WHEN the new spec removes nodes + // THEN the final tree matches the spec + applySpecAndCheck(node(controller5)) + } + + @Test + fun testReparentChildren() { + // GIVEN a preexisting tree of controllers + applySpecAndCheck( + node(controller1), + node(controller2, node(controller3), node(controller4)), + node(controller5) + ) + + // WHEN the parents of the controllers are all shuffled around + // THEN the final tree matches the spec + applySpecAndCheck( + node(controller1), + node(controller4), + node(controller3, node(controller2)) + ) + } + + @Test + fun testReorderChildren() { + // GIVEN a preexisting tree of controllers + applySpecAndCheck( + node(controller1), + node(controller2), + node(controller3), + node(controller4) + ) + + // WHEN the children change order + // THEN the final tree matches the spec + applySpecAndCheck( + node(controller3), + node(controller2), + node(controller4), + node(controller1) + ) + } + + @Test + fun testRemovedGroupsAreBrokenApart() { + // GIVEN a preexisting tree with a group + applySpecAndCheck( + node(controller1), + node(controller2, node(controller3), node(controller4), node(controller5)) + ) + + // WHEN the new spec removes the entire group + applySpecAndCheck(node(controller1)) + + // THEN the group children are no longer attached to their parent + Assert.assertNull(controller3.view.parent) + Assert.assertNull(controller4.view.parent) + Assert.assertNull(controller5.view.parent) + } + + @Test + fun testUnmanagedViews() { + // GIVEN a preexisting tree of controllers + applySpecAndCheck( + node(controller1), + node(controller2, node(controller3), node(controller4)), + node(controller5) + ) + + // GIVEN some additional unmanaged views attached to the tree + val unmanagedView1 = View(mContext) + val unmanagedView2 = View(mContext) + rootController.view.addView(unmanagedView1, 1) + controller2.view.addView(unmanagedView2, 0) + + // WHEN a new spec is applied with additional nodes + // THEN the final tree matches the spec + applySpecAndCheck( + node(controller1), + node(controller2, node(controller3), node(controller4), node(controller6)), + node(controller5), + node(controller7) + ) + + // THEN the unmanaged views have been pushed to the end of their parents + Assert.assertEquals(unmanagedView1, rootController.view.getChildAt(4)) + Assert.assertEquals(unmanagedView2, controller2.view.getChildAt(3)) + } + + private fun applySpecAndCheck(spec: NodeSpec) { + differ.applySpec(spec) + checkMatchesSpec(spec) + } + + private fun applySpecAndCheck(vararg children: SpecBuilder) { + applySpecAndCheck(node(rootController, *children).build()) + } + + private fun checkMatchesSpec(spec: NodeSpec) { + val parent = spec.controller + val children = spec.children + for (i in children.indices) { + val childSpec = children[i] + val view = parent.getChildAt(i) + Assert.assertEquals( + "Child $i of parent ${parent.nodeLabel} " + + "should be ${childSpec.controller.nodeLabel} " + + "but instead " + + view?.let(differ::getViewLabel), + view, + childSpec.controller.view + ) + if (childSpec.children.isNotEmpty()) { + checkMatchesSpec(childSpec) + } + } + } + + private class FakeController(context: Context, label: String) : NodeController { + override val view: FrameLayout = FrameLayout(context) + override val nodeLabel: String = label + override fun getChildCount(): Int = view.childCount + + override fun getChildAt(index: Int): View? { + return view.getChildAt(index) + } + + override fun addChildAt(child: NodeController, index: Int) { + view.addView(child.view, index) + } + + override fun moveChildTo(child: NodeController, index: Int) { + view.removeView(child.view) + view.addView(child.view, index) + } + + override fun removeChild(child: NodeController, isTransfer: Boolean) { + view.removeView(child.view) + } + + override fun onViewAdded() {} + override fun onViewMoved() {} + override fun onViewRemoved() {} + } + + private class SpecBuilder( + private val mController: NodeController, + private val children: Array<out SpecBuilder> + ) { + + @JvmOverloads + fun build(parent: NodeSpec? = null): NodeSpec { + val spec = NodeSpecImpl(parent, mController) + for (childBuilder in children) { + spec.children.add(childBuilder.build(spec)) + } + return spec + } + } + + companion object { + private fun node(controller: NodeController, vararg children: SpecBuilder): SpecBuilder { + return SpecBuilder(controller, children) + } + } +} 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 e7a435efbabe..112e7592f640 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 @@ -22,6 +22,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -60,6 +61,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.notification.ConversationNotificationProcessor; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection; @@ -192,6 +194,25 @@ public class NotificationTestHelper { } /** + * Creates a generic row with rounded border. + * + * @return a generic row with the set roundness. + * @throws Exception + */ + public ExpandableNotificationRow createRowWithRoundness( + float topRoundness, + float bottomRoundness, + SourceType sourceType + ) throws Exception { + ExpandableNotificationRow row = createRow(); + row.requestTopRoundness(topRoundness, false, sourceType); + row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType); + assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f); + assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f); + return row; + } + + /** * Creates a generic row. * * @return a generic row with no special properties. 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 7c41abba6176..438b528944be 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,6 +25,7 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationTestHelper; @@ -151,4 +152,37 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { Assert.assertNotNull("Children container must have a header after recreation", mChildrenContainer.getCurrentHeaderView()); } + + @Test + public void addNotification_shouldResetOnScrollRoundness() throws Exception { + ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnScroll); + + mChildrenContainer.addNotification(row, 0); + + Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f); + Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f); + } + + @Test + public void addNotification_shouldNotResetOtherRoundness() throws Exception { + ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.DefaultValue); + ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnDismissAnimation); + + mChildrenContainer.addNotification(row1, 0); + mChildrenContainer.addNotification(row2, 0); + + Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f); + Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f); + Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f); + Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f); + } } 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 77418138158b..bda233611158 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 @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.notification.stack import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress @@ -8,8 +9,10 @@ import com.android.systemui.SysuiTestCase 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.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals @@ -37,6 +40,13 @@ class NotificationShelfTest : SysuiTestCase() { private val shelfState = shelf.viewState as NotificationShelf.ShelfState private val ambientState = mock(AmbientState::class.java) private val hostLayoutController: NotificationStackScrollLayoutController = mock() + private val notificationTestHelper by lazy { + allowTestableLooperAsMainThread() + NotificationTestHelper( + mContext, + mDependency, + TestableLooper.get(this)) + } @Before fun setUp() { @@ -299,6 +309,39 @@ class NotificationShelfTest : SysuiTestCase() { ) } + @Test + fun resetOnScrollRoundness_shouldSetOnScrollTo0() { + val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnScroll) + + NotificationShelf.resetOnScrollRoundness(row) + + assertEquals(0f, row.topRoundness) + assertEquals(0f, row.bottomRoundness) + } + + @Test + fun resetOnScrollRoundness_shouldNotResetOtherRoundness() { + val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.DefaultValue) + val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness( + /* topRoundness = */ 1f, + /* bottomRoundness = */ 1f, + /* sourceType = */ SourceType.OnDismissAnimation) + + NotificationShelf.resetOnScrollRoundness(row1) + NotificationShelf.resetOnScrollRoundness(row2) + + assertEquals(1f, row1.topRoundness) + assertEquals(1f, row1.bottomRoundness) + assertEquals(1f, row2.topRoundness) + assertEquals(1f, row2.bottomRoundness) + } + private fun setFractionToShade(fraction: Float) { whenever(ambientState.fractionToShade).thenReturn(fraction) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 57557821cca6..7ce3a67ce835 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -500,7 +500,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mKeyguardVieMediatorCallback); // TODO: we should be able to call mCentralSurfaces.start() and have all the below values - // initialized automatically. + // initialized automatically and make NPVC private. mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView; mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController; mCentralSurfaces.mDozeScrimController = mDozeScrimController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 0c35659b458a..716666657871 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -307,7 +307,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() { - when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true); + when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true); mStatusBarKeyguardViewManager.onPanelExpansionChanged( expansionEvent( /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE, @@ -361,7 +361,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() { - when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true); + when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true); mStatusBarKeyguardViewManager.show(null); mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt index de1fec85360b..288f54c7d03c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt @@ -17,16 +17,18 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class FakeMobileConnectionRepository : MobileConnectionRepository { private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel()) - override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow + override val subscriptionModelFlow = _subscriptionsModelFlow private val _dataEnabled = MutableStateFlow(true) override val dataEnabled = _dataEnabled + private val _isDefaultDataSubscription = MutableStateFlow(true) + override val isDefaultDataSubscription = _isDefaultDataSubscription + fun setMobileSubscriptionModel(model: MobileSubscriptionModel) { _subscriptionsModelFlow.value = model } @@ -34,4 +36,8 @@ class FakeMobileConnectionRepository : MobileConnectionRepository { fun setDataEnabled(enabled: Boolean) { _dataEnabled.value = enabled } + + fun setIsDefaultDataSubscription(isDefault: Boolean) { + _isDefaultDataSubscription.value = isDefault + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index 813e750684a0..533d5d9d5b4a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -17,8 +17,9 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository import android.telephony.SubscriptionInfo -import android.telephony.SubscriptionManager +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import com.android.settingslib.mobile.MobileMappings.Config +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -26,18 +27,26 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf()) override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow - private val _activeMobileDataSubscriptionId = - MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID) + private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId private val _defaultDataSubRatConfig = MutableStateFlow(Config()) override val defaultDataSubRatConfig = _defaultDataSubRatConfig + private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID) + override val defaultDataSubId = _defaultDataSubId + + private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel()) + override val defaultMobileNetworkConnectivity = _mobileConnectivity + private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>() override fun getRepoForSubId(subId: Int): MobileConnectionRepository { return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it } } + private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit) + override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent + fun setSubscriptions(subs: List<SubscriptionInfo>) { _subscriptionsFlow.value = subs } @@ -46,6 +55,18 @@ class FakeMobileConnectionsRepository : MobileConnectionsRepository { _defaultDataSubRatConfig.value = config } + fun setDefaultDataSubId(id: Int) { + _defaultDataSubId.value = id + } + + fun setMobileConnectivity(model: MobileConnectivityModel) { + _mobileConnectivity.value = model + } + + suspend fun triggerGlobalMobileDataSettingChangedEvent() { + _globalMobileDataSettingChangedEvent.emit(Unit) + } + fun setActiveMobileDataSubscriptionId(subId: Int) { _activeMobileDataSubscriptionId.value = subId } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt index 6c495c5c705a..141b50c017e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt @@ -16,13 +16,12 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow /** Defaults to `true` */ class FakeUserSetupRepository : UserSetupRepository { private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true) - override val isUserSetupFlow: Flow<Boolean> = _isUserSetup + override val isUserSetupFlow = _isUserSetup fun setUserSetup(setup: Boolean) { _isUserSetup.value = setup 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 093936444789..5ce51bb62c78 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 @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.os.UserHandle +import android.provider.Settings import android.telephony.CellSignalStrengthCdma import android.telephony.ServiceState import android.telephony.SignalStrength @@ -42,6 +44,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -67,16 +70,23 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Mock private lateinit var logger: ConnectivityPipelineLogger private val scope = CoroutineScope(IMMEDIATE) + private val globalSettings = FakeSettings() + private val connectionsRepo = FakeMobileConnectionsRepository() @Before fun setUp() { MockitoAnnotations.initMocks(this) + globalSettings.userId = UserHandle.USER_ALL whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID) underTest = MobileConnectionRepositoryImpl( + context, SUB_1_ID, telephonyManager, + globalSettings, + connectionsRepo.defaultDataSubId, + connectionsRepo.globalMobileDataSettingChangedEvent, IMMEDIATE, logger, scope, @@ -290,14 +300,20 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { } @Test - fun dataEnabled_isEnabled() = + fun dataEnabled_initial_false() = runBlocking(IMMEDIATE) { whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true) - var latest: Boolean? = null - val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) + assertThat(underTest.dataEnabled.value).isFalse() + } - assertThat(latest).isTrue() + @Test + fun dataEnabled_isEnabled_true() = + runBlocking(IMMEDIATE) { + whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true) + val job = underTest.dataEnabled.launchIn(this) + + assertThat(underTest.dataEnabled.value).isTrue() job.cancel() } @@ -306,10 +322,59 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { fun dataEnabled_isDisabled() = runBlocking(IMMEDIATE) { whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false) + val job = underTest.dataEnabled.launchIn(this) + + assertThat(underTest.dataEnabled.value).isFalse() + + job.cancel() + } + + @Test + fun isDefaultDataSubscription_isDefault() = + runBlocking(IMMEDIATE) { + connectionsRepo.setDefaultDataSubId(SUB_1_ID) + + var latest: Boolean? = null + val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun isDefaultDataSubscription_isNotDefault() = + runBlocking(IMMEDIATE) { + // Our subId is SUB_1_ID + connectionsRepo.setDefaultDataSubId(123) + + var latest: Boolean? = null + val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() = + runBlocking(IMMEDIATE) { + val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID" var latest: Boolean? = null val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) + // We don't read the setting directly, we query telephony when changes happen + whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false) + globalSettings.putInt(subIdSettingName, 0) + assertThat(latest).isFalse() + + whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true) + globalSettings.putInt(subIdSettingName, 1) + assertThat(latest).isTrue() + + whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false) + globalSettings.putInt(subIdSettingName, 0) assertThat(latest).isFalse() job.cancel() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt index 326e0d28166f..a953a3d802e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt @@ -16,26 +16,33 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository +import android.content.Intent +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED +import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.provider.Settings import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyCallback import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener import android.telephony.TelephonyManager import androidx.test.filters.SmallTest +import com.android.internal.telephony.PhoneConstants import com.android.systemui.SysuiTestCase -import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking @@ -43,7 +50,6 @@ import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test -import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -54,32 +60,26 @@ import org.mockito.MockitoAnnotations class MobileConnectionsRepositoryTest : SysuiTestCase() { private lateinit var underTest: MobileConnectionsRepositoryImpl + @Mock private lateinit var connectivityManager: ConnectivityManager @Mock private lateinit var subscriptionManager: SubscriptionManager @Mock private lateinit var telephonyManager: TelephonyManager @Mock private lateinit var logger: ConnectivityPipelineLogger - @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher private val scope = CoroutineScope(IMMEDIATE) + private val globalSettings = FakeSettings() @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever( - broadcastDispatcher.broadcastFlow( - any(), - nullable(), - ArgumentMatchers.anyInt(), - nullable(), - ) - ) - .thenReturn(flowOf(Unit)) underTest = MobileConnectionsRepositoryImpl( + connectivityManager, subscriptionManager, telephonyManager, logger, - broadcastDispatcher, + fakeBroadcastDispatcher, + globalSettings, context, IMMEDIATE, scope, @@ -214,6 +214,139 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { job.cancel() } + @Test + fun testDefaultDataSubId_updatesOnBroadcast() = + runBlocking(IMMEDIATE) { + var latest: Int? = null + val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this) + + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive( + context, + Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID) + ) + } + + assertThat(latest).isEqualTo(SUB_2_ID) + + fakeBroadcastDispatcher.registeredReceivers.forEach { receiver -> + receiver.onReceive( + context, + Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED) + .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID) + ) + } + + assertThat(latest).isEqualTo(SUB_1_ID) + + job.cancel() + } + + @Test + fun mobileConnectivity_default() { + assertThat(underTest.defaultMobileNetworkConnectivity.value) + .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false)) + } + + @Test + fun mobileConnectivity_isConnected_isValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = true, validated = true) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest) + .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true)) + + job.cancel() + } + + @Test + fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() = + runBlocking(IMMEDIATE) { + var produced = false + val job = + underTest.globalMobileDataSettingChangedEvent + .onEach { produced = true } + .launchIn(this) + + assertThat(produced).isFalse() + + globalSettings.putInt(Settings.Global.MOBILE_DATA, 0) + + assertThat(produced).isTrue() + + job.cancel() + } + + @Test + fun mobileConnectivity_isConnected_isNotValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = true, validated = false) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest) + .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false)) + + job.cancel() + } + + @Test + fun mobileConnectivity_isNotConnected_isNotValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = false, validated = false) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest) + .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false)) + + job.cancel() + } + + /** In practice, I don't think this state can ever happen (!connected, validated) */ + @Test + fun mobileConnectivity_isNotConnected_isValidated() = + runBlocking(IMMEDIATE) { + val caps = createCapabilities(connected = false, validated = true) + + var latest: MobileConnectivityModel? = null + val job = + underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this) + + getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps) + + assertThat(latest).isEqualTo(MobileConnectivityModel(false, true)) + + job.cancel() + } + + private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities = + mock<NetworkCapabilities>().also { + whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected) + whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated) + } + + private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback { + val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>() + verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture()) + return callbackCaptor.value!! + } + private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener { val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>() verify(subscriptionManager) @@ -242,5 +375,8 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { private const val SUB_2_ID = 2 private val SUB_2 = mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) } + + private const val NET_ID = 123 + private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 5611c448c550..3ae7d3ca1c19 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -28,18 +28,23 @@ class FakeMobileIconInteractor : MobileIconInteractor { private val _isEmergencyOnly = MutableStateFlow(false) override val isEmergencyOnly = _isEmergencyOnly + private val _isFailedConnection = MutableStateFlow(false) + override val isDefaultConnectionFailed = _isFailedConnection + + override val isDataConnected = MutableStateFlow(true) + private val _isDataEnabled = MutableStateFlow(true) override val isDataEnabled = _isDataEnabled + private val _isDefaultDataEnabled = MutableStateFlow(true) + override val isDefaultDataEnabled = _isDefaultDataEnabled + private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) override val level = _level private val _numberOfLevels = MutableStateFlow(4) override val numberOfLevels = _numberOfLevels - private val _cutOut = MutableStateFlow(false) - override val cutOut = _cutOut - fun setIconGroup(group: SignalIcon.MobileIconGroup) { _iconGroup.value = group } @@ -52,6 +57,14 @@ class FakeMobileIconInteractor : MobileIconInteractor { _isDataEnabled.value = enabled } + fun setIsDefaultDataEnabled(disabled: Boolean) { + _isDefaultDataEnabled.value = disabled + } + + fun setIsFailedConnection(failed: Boolean) { + _isFailedConnection.value = failed + } + fun setLevel(level: Int) { _level.value = level } @@ -59,8 +72,4 @@ class FakeMobileIconInteractor : MobileIconInteractor { fun setNumberOfLevels(num: Int) { _numberOfLevels.value = num } - - fun setCutOut(cutOut: Boolean) { - _cutOut.value = cutOut - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 2bd228603cb0..061c3b54650e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -26,8 +26,7 @@ import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import kotlinx.coroutines.flow.MutableStateFlow -class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) : - MobileIconsInteractor { +class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor { val THREE_G_KEY = mobileMappings.toIconKey(THREE_G) val LTE_KEY = mobileMappings.toIconKey(LTE) val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G) @@ -46,9 +45,14 @@ class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G, ) + override val isDefaultConnectionFailed = MutableStateFlow(false) + private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf()) override val filteredSubscriptions = _filteredSubscriptions + private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false) + override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled + private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING) override val defaultMobileIconMapping = _defaultMobileIconMapping diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index ff44af4c9204..7fc1c0f6272c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType @@ -34,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsPro import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -49,12 +51,17 @@ class MobileIconInteractorTest : SysuiTestCase() { private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy) private val connectionRepository = FakeMobileConnectionRepository() + private val scope = CoroutineScope(IMMEDIATE) + @Before fun setUp() { underTest = MobileIconInteractorImpl( + scope, + mobileIconsInteractor.activeDataConnectionHasDataEnabled, mobileIconsInteractor.defaultMobileIconMapping, mobileIconsInteractor.defaultMobileIconGroup, + mobileIconsInteractor.isDefaultConnectionFailed, mobileMappingsProxy, connectionRepository, ) @@ -196,6 +203,66 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun test_isDefaultDataEnabled_matchesParent() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this) + + mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true + assertThat(latest).isTrue() + + mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun test_isDefaultConnectionFailed_matchedParent() = + runBlocking(IMMEDIATE) { + val job = underTest.isDefaultConnectionFailed.launchIn(this) + + mobileIconsInteractor.isDefaultConnectionFailed.value = false + assertThat(underTest.isDefaultConnectionFailed.value).isFalse() + + mobileIconsInteractor.isDefaultConnectionFailed.value = true + assertThat(underTest.isDefaultConnectionFailed.value).isTrue() + + job.cancel() + } + + @Test + fun dataState_connected() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) + + connectionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected) + ) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun dataState_notConnected() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this) + + connectionRepository.setMobileSubscriptionModel( + MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected) + ) + + assertThat(latest).isFalse() + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 877ce0e6b351..b56dcd752557 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository @@ -32,6 +34,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.yield import org.junit.After import org.junit.Before import org.junit.Test @@ -168,6 +171,92 @@ class MobileIconsInteractorTest : SysuiTestCase() { job.cancel() } + @Test + fun activeDataConnection_turnedOn() = + runBlocking(IMMEDIATE) { + CONNECTION_1.setDataEnabled(true) + var latest: Boolean? = null + val job = + underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun activeDataConnection_turnedOff() = + runBlocking(IMMEDIATE) { + CONNECTION_1.setDataEnabled(true) + var latest: Boolean? = null + val job = + underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + + CONNECTION_1.setDataEnabled(false) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun activeDataConnection_invalidSubId() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = + underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + + connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID) + yield() + + // An invalid active subId should tell us that data is off + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun failedConnection_connected_validated_notFailed() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true)) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun failedConnection_notConnected_notValidated_notFailed() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + + connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false)) + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun failedConnection_connected_notValidated_failed() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + + connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false)) + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index ce0f33f400ab..d4c2c3f6cc2b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -46,10 +46,12 @@ class MobileIconViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) interactor.apply { setLevel(1) - setCutOut(false) + setIsDefaultDataEnabled(true) + setIsFailedConnection(false) setIconGroup(THREE_G) setIsEmergencyOnly(false) setNumberOfLevels(4) + isDataConnected.value = true } underTest = MobileIconViewModel(SUB_1_ID, interactor, logger) } @@ -59,8 +61,23 @@ class MobileIconViewModelTest : SysuiTestCase() { runBlocking(IMMEDIATE) { var latest: Int? = null val job = underTest.iconId.onEach { latest = it }.launchIn(this) + val expected = defaultSignal() - assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false)) + assertThat(latest).isEqualTo(expected) + + job.cancel() + } + + @Test + fun iconId_cutout_whenDefaultDataDisabled() = + runBlocking(IMMEDIATE) { + interactor.setIsDefaultDataEnabled(false) + + var latest: Int? = null + val job = underTest.iconId.onEach { latest = it }.launchIn(this) + val expected = defaultSignal(level = 1, connected = false) + + assertThat(latest).isEqualTo(expected) job.cancel() } @@ -97,6 +114,44 @@ class MobileIconViewModelTest : SysuiTestCase() { } @Test + fun networkType_nullWhenFailedConnection() = + runBlocking(IMMEDIATE) { + interactor.setIconGroup(THREE_G) + interactor.setIsDataEnabled(true) + interactor.setIsFailedConnection(true) + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + assertThat(latest).isNull() + + job.cancel() + } + + @Test + fun networkType_nullWhenDataDisconnects() = + runBlocking(IMMEDIATE) { + val initial = + Icon.Resource( + THREE_G.dataType, + ContentDescription.Resource(THREE_G.dataContentDescription) + ) + + interactor.setIconGroup(THREE_G) + var latest: Icon? = null + val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.setIconGroup(THREE_G) + assertThat(latest).isEqualTo(initial) + + interactor.isDataConnected.value = false + yield() + + assertThat(latest).isNull() + + job.cancel() + } + + @Test fun networkType_null_changeToDisabled() = runBlocking(IMMEDIATE) { val expected = @@ -119,6 +174,14 @@ class MobileIconViewModelTest : SysuiTestCase() { job.cancel() } + /** Convenience constructor for these tests */ + private fun defaultSignal( + level: Int = 1, + connected: Boolean = true, + ): Int { + return SignalDrawable.getState(level, /* numLevels */ 4, !connected) + } + companion object { private val IMMEDIATE = Dispatchers.Main.immediate private const val SUB_1_ID = 1 diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt index 120bf791c462..e49652148f3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt @@ -219,6 +219,7 @@ class GuestUserInteractorTest : SysuiTestCase() { repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO)) repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO) val targetUserId = NON_GUEST_USER_INFO.id + val ephemeralGuestUserHandle = UserHandle.of(EPHEMERAL_GUEST_USER_INFO.id) underTest.exit( guestUserId = GUEST_USER_INFO.id, @@ -230,7 +231,7 @@ class GuestUserInteractorTest : SysuiTestCase() { ) verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id) - verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id) + verify(manager).removeUserWhenPossible(ephemeralGuestUserHandle, false) verify(switchUser).invoke(targetUserId) } @@ -240,6 +241,7 @@ class GuestUserInteractorTest : SysuiTestCase() { whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true) repository.setSelectedUserInfo(GUEST_USER_INFO) val targetUserId = NON_GUEST_USER_INFO.id + val guestUserHandle = UserHandle.of(GUEST_USER_INFO.id) underTest.exit( guestUserId = GUEST_USER_INFO.id, @@ -251,7 +253,7 @@ class GuestUserInteractorTest : SysuiTestCase() { ) verify(manager).markGuestForDeletion(GUEST_USER_INFO.id) - verify(manager).removeUser(GUEST_USER_INFO.id) + verify(manager).removeUserWhenPossible(guestUserHandle, false) verify(switchUser).invoke(targetUserId) } @@ -296,6 +298,7 @@ class GuestUserInteractorTest : SysuiTestCase() { repository.setSelectedUserInfo(GUEST_USER_INFO) val targetUserId = NON_GUEST_USER_INFO.id + val guestUserHandle = UserHandle.of(GUEST_USER_INFO.id) underTest.remove( guestUserId = GUEST_USER_INFO.id, targetUserId = targetUserId, @@ -305,7 +308,7 @@ class GuestUserInteractorTest : SysuiTestCase() { ) verify(manager).markGuestForDeletion(GUEST_USER_INFO.id) - verify(manager).removeUser(GUEST_USER_INFO.id) + verify(manager).removeUserWhenPossible(guestUserHandle, false) verify(switchUser).invoke(targetUserId) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java index c2543589bfb8..379bb28ae032 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java @@ -26,8 +26,8 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -44,6 +44,7 @@ import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.os.Handler; +import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; @@ -135,9 +136,10 @@ public class ImageWallpaperTest extends SysuiTestCase { when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight); // set up wallpaper manager - when(mWallpaperManager.peekBitmapDimensions()).thenReturn( - new Rect(0, 0, mBitmapWidth, mBitmapHeight)); - when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap); + when(mWallpaperManager.peekBitmapDimensions()) + .thenReturn(new Rect(0, 0, mBitmapWidth, mBitmapHeight)); + when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean())) + .thenReturn(mWallpaperBitmap); when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager); // set up surface @@ -286,9 +288,6 @@ public class ImageWallpaperTest extends SysuiTestCase { testMinSurfaceHelper(8, 8); testMinSurfaceHelper(100, 2000); testMinSurfaceHelper(200, 1); - testMinSurfaceHelper(0, 1); - testMinSurfaceHelper(1, 0); - testMinSurfaceHelper(0, 0); } private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) { @@ -307,28 +306,6 @@ public class ImageWallpaperTest extends SysuiTestCase { } @Test - public void testZeroBitmap() { - // test that a frame is never drawn with a 0 bitmap - testZeroBitmapHelper(0, 1); - testZeroBitmapHelper(1, 0); - testZeroBitmapHelper(0, 0); - } - - private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) { - - clearInvocations(mSurfaceHolder); - setBitmapDimensions(bitmapWidth, bitmapHeight); - - ImageWallpaper imageWallpaper = createImageWallpaperCanvas(); - ImageWallpaper.CanvasEngine engine = - (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine(); - ImageWallpaper.CanvasEngine spyEngine = spy(engine); - spyEngine.onCreate(mSurfaceHolder); - spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder); - verify(spyEngine, never()).drawFrameOnCanvas(any()); - } - - @Test public void testLoadDrawAndUnloadBitmap() { setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 0c126805fb78..11178db8afd2 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.common.shared.model.Position import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakefulnessModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -48,6 +49,9 @@ class FakeKeyguardRepository : KeyguardRepository { private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) override val statusBarState: Flow<StatusBarState> = _statusBarState + private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP) + override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState + override fun isKeyguardShowing(): Boolean { return _isKeyguardShowing.value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt new file mode 100644 index 000000000000..6c4424470c1c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import android.annotation.FloatRange +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import java.util.UUID +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow + +/** Fake implementation of [KeyguardTransitionRepository] */ +class FakeKeyguardTransitionRepository : KeyguardTransitionRepository { + + private val _transitions = MutableSharedFlow<TransitionStep>() + override val transitions: SharedFlow<TransitionStep> = _transitions + + suspend fun sendTransitionStep(step: TransitionStep) { + _transitions.emit(step) + } + + override fun startTransition(info: TransitionInfo): UUID? { + return null + } + + override fun updateTransition( + transitionId: UUID, + @FloatRange(from = 0.0, to = 1.0) value: Float, + state: TransitionState + ) = Unit +} diff --git a/packages/VpnDialogs/res/values-es-rUS/strings.xml b/packages/VpnDialogs/res/values-es-rUS/strings.xml index 108a24e7930e..232b53ab3d48 100644 --- a/packages/VpnDialogs/res/values-es-rUS/strings.xml +++ b/packages/VpnDialogs/res/values-es-rUS/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN capaz de controlar el tráfico de la red. Acéptala solo si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparece en la parte superior de la pantalla cuando se activa la VPN."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN capaz de supervisar el tráfico de la red. Acéptala solo si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparece en la parte superior de la pantalla cuando se activa la VPN."</string> <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita supervisar el tráfico de red. Solo acéptala si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparecerá en tu pantalla cuando se active la VPN."</string> <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada."</string> <string name="session" msgid="6470628549473641030">"Sesión:"</string> diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml index 9bf86f51f8fe..4e21fd099892 100644 --- a/packages/VpnDialogs/res/values-es/strings.xml +++ b/packages/VpnDialogs/res/values-es/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparece en la parte superior de la pantalla cuando se active la conexión VPN."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparece en la parte superior de la pantalla cuando la conexión VPN está activa."</string> <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita monitorizar el tráfico de red. Acéptalo solo si confías en la fuente. <br /> <br /> <img src=vpn_icon /> aparecerá en la pantalla cuando la VPN esté activa."</string> <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada"</string> <string name="session" msgid="6470628549473641030">"Sesión:"</string> diff --git a/packages/VpnDialogs/res/values-nl/strings.xml b/packages/VpnDialogs/res/values-nl/strings.xml index 33f8a89a1562..76f56afb63df 100644 --- a/packages/VpnDialogs/res/values-nl/strings.xml +++ b/packages/VpnDialogs/res/values-nl/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="prompt" msgid="3183836924226407828">"Verbindingsverzoek"</string> - <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding opzetten om netwerkverkeer te controleren. Accepteer het verzoek alleen als je de bron vertrouwt. <br /> <br /> <img src=vpn_icon /> wordt boven aan je scherm weergegeven wanneer VPN actief is."</string> + <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding instellen waarmee de app het netwerkverkeer kan bijhouden. Accepteer dit alleen als je de bron vertrouwt. <br /> <br /> <img src=vpn_icon /> verschijnt op je scherm als het VPN actief is."</string> <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding instellen waarmee de app het netwerkverkeer kan bijhouden. Accepteer dit alleen als je de bron vertrouwt. <br /> <br /> <img src=vpn_icon /> verschijnt op je scherm als het VPN actief is."</string> <string name="legacy_title" msgid="192936250066580964">"Verbinding met VPN"</string> <string name="session" msgid="6470628549473641030">"Sessie:"</string> diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 47b415630de8..e3ae03cbcdd8 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -3651,6 +3651,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub throw new IllegalArgumentException("The display " + displayId + " does not exist or is" + " not tracked by accessibility."); } + if (mProxyManager.isProxyed(displayId)) { + throw new IllegalArgumentException("The display " + displayId + " is already being" + + "proxy-ed"); + } mProxyManager.registerProxy(client, displayId); return true; diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index 934b665d7dae..247f320adecd 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -32,6 +32,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; import android.view.KeyEvent; +import android.view.accessibility.AccessibilityDisplayProxy; import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.Nullable; @@ -44,7 +45,7 @@ import java.util.List; import java.util.Set; /** - * Represents the system connection to an {@link android.view.accessibility.AccessibilityProxy}. + * Represents the system connection to an {@link AccessibilityDisplayProxy}. * * <p>Most methods are no-ops since this connection does not need to capture input or listen to * hardware-related changes. diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java index fb0b8f3b17b1..a2ce61063aaf 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; import android.accessibilityservice.IAccessibilityServiceClient; +import java.util.HashSet; + /** * Manages proxy connections. * @@ -26,6 +28,7 @@ import android.accessibilityservice.IAccessibilityServiceClient; */ public class ProxyManager { private final Object mLock; + private final HashSet<Integer> mDisplayIds = new HashSet<>(); ProxyManager(Object lock) { mLock = lock; @@ -35,12 +38,21 @@ public class ProxyManager { * TODO: Create the proxy service connection. */ public void registerProxy(IAccessibilityServiceClient client, int displayId) { + mDisplayIds.add(displayId); } /** * TODO: Unregister the proxy service connection based on display id. */ public boolean unregisterProxy(int displayId) { + mDisplayIds.remove(displayId); return true; } + + /** + * Checks if a display id is being proxy-ed. + */ + public boolean isProxyed(int displayId) { + return mDisplayIds.contains(displayId); + } } diff --git a/services/art-profile b/services/art-profile index 3e0507824572..b6398c00cf3b 100644 --- a/services/art-profile +++ b/services/art-profile @@ -41551,7 +41551,7 @@ PLcom/android/server/statusbar/StatusBarManagerService$1;->setDisableFlags(IILja HPLcom/android/server/statusbar/StatusBarManagerService$1;->setNavigationBarLumaSamplingEnabled(IZ)V HSPLcom/android/server/statusbar/StatusBarManagerService$1;->setNotificationDelegate(Lcom/android/server/notification/NotificationDelegate;)V HPLcom/android/server/statusbar/StatusBarManagerService$1;->setTopAppHidesStatusBar(Z)V+]Lcom/android/internal/statusbar/IStatusBar;Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy; -PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V +PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V HPLcom/android/server/statusbar/StatusBarManagerService$1;->setWindowState(III)V PLcom/android/server/statusbar/StatusBarManagerService$1;->showChargingAnimation(I)V PLcom/android/server/statusbar/StatusBarManagerService$1;->showRecentApps(Z)V @@ -41677,6 +41677,11 @@ PLcom/android/server/statusbar/StatusBarManagerService;->setDisableFlags(IILjava PLcom/android/server/statusbar/StatusBarManagerService;->setIcon(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V +PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V +PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V +HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService; +HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V+]Landroid/os/Handler;Landroid/os/Handler;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService; +PLcom/android/server/statusbar/StatusBarManagerService;->setNavBarMode(I)V PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V PLcom/android/server/statusbar/StatusBarManagerService;->showAuthenticationDialog(Landroid/hardware/biometrics/PromptInfo;Landroid/hardware/biometrics/IBiometricSysuiReceiver;[IZZIJLjava/lang/String;JI)V PLcom/android/server/statusbar/StatusBarManagerService;->suppressAmbientDisplay(Z)V diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 47ce5928c0be..64b7688cc196 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -24,6 +24,7 @@ import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU; import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN; import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST; import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE; +import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE; import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; @@ -416,6 +417,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private boolean mPreviouslyFillDialogPotentiallyStarted; + /** + * Keeps the fill dialog trigger ids of the last response. This invalidates + * the trigger ids of the previous response. + */ + @Nullable + @GuardedBy("mLock") + private AutofillId[] mLastFillDialogTriggerIds; + void onSwitchInputMethodLocked() { // One caveat is that for the case where the focus is on a field for which regular autofill // returns null, and augmented autofill is triggered, and then the user switches the input @@ -1222,6 +1231,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } + mLastFillDialogTriggerIds = response.getFillDialogTriggerIds(); + final int flags = response.getFlags(); if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) { Slog.v(TAG, "Service requested to wait for delayed fill response."); @@ -1310,6 +1321,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // fallback to the default platform password manager mSessionFlags.mClientSuggestionsEnabled = false; + mLastFillDialogTriggerIds = null; final InlineSuggestionsRequest inlineRequest = (mLastInlineSuggestionsRequest != null @@ -1348,6 +1360,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + (timedOut ? "timeout" : "failure")); } mService.resetLastResponse(); + mLastFillDialogTriggerIds = null; final LogMaker requestLog = mRequestLogs.get(requestId); if (requestLog == null) { Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId); @@ -3049,6 +3062,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + if ((flags & FLAG_RESET_FILL_DIALOG_STATE) != 0) { + if (sDebug) Log.d(TAG, "force to reset fill dialog state"); + mSessionFlags.mFillDialogDisabled = false; + } + switch(action) { case ACTION_START_SESSION: // View is triggering autofill. @@ -3488,10 +3506,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private boolean isFillDialogUiEnabled() { - // TODO read from Settings or somewhere - final boolean isSettingsEnabledFillDialog = true; synchronized (mLock) { - return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled; + return !mSessionFlags.mFillDialogDisabled; } } @@ -3517,14 +3533,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState AutofillId filledId, String filterText, int flags) { if (!isFillDialogUiEnabled()) { // Unsupported fill dialog UI + if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled"); return false; } if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) { // IME is showing, fallback to normal suggestions UI + if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing"); return false; } + synchronized (mLock) { + if (mLastFillDialogTriggerIds == null + || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) { + // Last fill dialog triggered ids are changed. + if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); + return false; + } + } + final Drawable serviceIcon = getServiceIcon(); getUiForShowing().showFillDialog(filledId, response, filterText, @@ -4394,6 +4421,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mSessionFlags.mAugmentedAutofillOnly) { pw.print(prefix); pw.println("For Augmented Autofill Only"); } + if (mSessionFlags.mFillDialogDisabled) { + pw.print(prefix); pw.println("Fill Dialog disabled"); + } + if (mLastFillDialogTriggerIds != null) { + pw.print(prefix); pw.println("Last Fill Dialog trigger ids: "); + pw.println(mSelectedDatasetIds); + } if (mAugmentedAutofillDestroyer != null) { pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); } diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java index cee78e0e9ab5..34787a390d48 100644 --- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java +++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java @@ -728,7 +728,7 @@ final class ContentCapturePerUserService if (oldList != null && adding != null) { adding.removeAll(oldList); } - + addingCount = CollectionUtils.size(adding); EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, addingCount); for (int i = 0; i < addingCount; i++) { String packageName = adding.valueAt(i); diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java index 41044bfc70ca..b70cbe329f3a 100644 --- a/services/core/java/android/os/BatteryStatsInternal.java +++ b/services/core/java/android/os/BatteryStatsInternal.java @@ -27,7 +27,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.Collection; import java.util.List; - /** * Battery stats local system service interface. This is used to pass internal data out of * BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl. diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java index a9bdf063e8a6..ee6d808aa549 100644 --- a/services/core/java/com/android/server/ConsumerIrService.java +++ b/services/core/java/com/android/server/ConsumerIrService.java @@ -92,6 +92,8 @@ public class ConsumerIrService extends IConsumerIrService.Stub { @Override @EnforcePermission(TRANSMIT_IR) public void transmit(String packageName, int carrierFrequency, int[] pattern) { + super.transmit_enforcePermission(); + long totalXmitTime = 0; for (int slice : pattern) { @@ -128,6 +130,8 @@ public class ConsumerIrService extends IConsumerIrService.Stub { @Override @EnforcePermission(TRANSMIT_IR) public int[] getCarrierFrequencies() { + super.getCarrierFrequencies_enforcePermission(); + throwIfNoIrEmitter(); synchronized(mHalLock) { diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java index ce0e69c40f67..27215b2fbf30 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -77,6 +77,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean startInstallation(String dsuSlot) throws RemoteException { + super.startInstallation_enforcePermission(); + IGsiService service = getGsiService(); mGsiService = service; // priority from high to low: sysprop -> sdcard -> /data @@ -124,6 +126,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public int createPartition(String name, long size, boolean readOnly) throws RemoteException { + super.createPartition_enforcePermission(); + IGsiService service = getGsiService(); int status = service.createPartition(name, size, readOnly); if (status != IGsiService.INSTALL_OK) { @@ -135,6 +139,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean closePartition() throws RemoteException { + super.closePartition_enforcePermission(); + IGsiService service = getGsiService(); if (service.closePartition() != 0) { Slog.i(TAG, "Partition installation completes with error"); @@ -146,6 +152,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean finishInstallation() throws RemoteException { + super.finishInstallation_enforcePermission(); + IGsiService service = getGsiService(); if (service.closeInstall() != 0) { Slog.i(TAG, "Failed to finish installation"); @@ -157,12 +165,16 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public GsiProgress getInstallationProgress() throws RemoteException { + super.getInstallationProgress_enforcePermission(); + return getGsiService().getInstallProgress(); } @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean abort() throws RemoteException { + super.abort_enforcePermission(); + return getGsiService().cancelGsiInstall(); } @@ -183,12 +195,16 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean isEnabled() throws RemoteException { + super.isEnabled_enforcePermission(); + return getGsiService().isGsiEnabled(); } @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean remove() throws RemoteException { + super.remove_enforcePermission(); + try { GsiServiceCallback callback = new GsiServiceCallback(); synchronized (callback) { @@ -205,6 +221,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException { + super.setEnable_enforcePermission(); + IGsiService gsiService = getGsiService(); if (enable) { try { @@ -229,6 +247,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean setAshmem(ParcelFileDescriptor ashmem, long size) { + super.setAshmem_enforcePermission(); + try { return getGsiService().setGsiAshmem(ashmem, size); } catch (RemoteException e) { @@ -239,6 +259,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean submitFromAshmem(long size) { + super.submitFromAshmem_enforcePermission(); + try { return getGsiService().commitGsiChunkFromAshmem(size); } catch (RemoteException e) { @@ -249,6 +271,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public boolean getAvbPublicKey(AvbPublicKey dst) { + super.getAvbPublicKey_enforcePermission(); + try { return getGsiService().getAvbPublicKey(dst) == 0; } catch (RemoteException e) { @@ -259,6 +283,8 @@ public class DynamicSystemService extends IDynamicSystemService.Stub { @Override @EnforcePermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM) public long suggestScratchSize() throws RemoteException { + super.suggestScratchSize_enforcePermission(); + return getGsiService().suggestScratchSize(); } } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index d29e25c3faff..5d54b6c1c81f 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -867,6 +867,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { public void shutdown() { // TODO: remove from aidl if nobody calls externally + super.shutdown_enforcePermission(); + Slog.i(TAG, "Shutting down"); } @@ -1207,6 +1209,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @Override public boolean setDataSaverModeEnabled(boolean enable) { + super.setDataSaverModeEnabled_enforcePermission(); + if (DBG) Log.d(TAG, "setDataSaverMode: " + enable); synchronized (mQuotaLock) { if (mDataSaverMode == enable) { @@ -1744,6 +1748,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) @Override public boolean isNetworkRestricted(int uid) { + super.isNetworkRestricted_enforcePermission(); + return isNetworkRestrictedInternal(uid); } diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java index e915fa1522a1..ff903a0b4c24 100644 --- a/services/core/java/com/android/server/SerialService.java +++ b/services/core/java/com/android/server/SerialService.java @@ -37,6 +37,8 @@ public class SerialService extends ISerialManager.Stub { @EnforcePermission(android.Manifest.permission.SERIAL_PORT) public String[] getSerialPorts() { + super.getSerialPorts_enforcePermission(); + ArrayList<String> ports = new ArrayList<String>(); for (int i = 0; i < mSerialPorts.length; i++) { String path = mSerialPorts[i]; @@ -51,6 +53,8 @@ public class SerialService extends ISerialManager.Stub { @EnforcePermission(android.Manifest.permission.SERIAL_PORT) public ParcelFileDescriptor openSerialPort(String path) { + super.openSerialPort_enforcePermission(); + for (int i = 0; i < mSerialPorts.length; i++) { if (mSerialPorts[i].equals(path)) { return native_open(path); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 72876f669f01..fcfee5b702ac 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -1251,6 +1251,8 @@ class StorageManagerService extends IStorageManager.Stub // Binder entry point for kicking off an immediate fstrim @Override public void runMaintenance() { + super.runMaintenance_enforcePermission(); + runIdleMaintenance(null); } @@ -2167,6 +2169,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void shutdown(final IStorageShutdownObserver observer) { + super.shutdown_enforcePermission(); + Slog.i(TAG, "Shutting down"); mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget(); } @@ -2175,6 +2179,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void mount(String volId) { + super.mount_enforcePermission(); + final VolumeInfo vol = findVolumeByIdOrThrow(volId); if (isMountDisallowed(vol)) { throw new SecurityException("Mounting " + volId + " restricted by policy"); @@ -2243,6 +2249,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void unmount(String volId) { + super.unmount_enforcePermission(); + final VolumeInfo vol = findVolumeByIdOrThrow(volId); unmount(vol); } @@ -2267,6 +2275,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void format(String volId) { + super.format_enforcePermission(); + final VolumeInfo vol = findVolumeByIdOrThrow(volId); final String fsUuid = vol.fsUuid; try { @@ -2286,6 +2296,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void benchmark(String volId, IVoldTaskListener listener) { + super.benchmark_enforcePermission(); + try { mVold.benchmark(volId, new IVoldTaskListener.Stub() { @Override @@ -2325,6 +2337,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void partitionPublic(String diskId) { + super.partitionPublic_enforcePermission(); + final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); try { mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1); @@ -2337,6 +2351,8 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) @Override public void partitionPrivate(String diskId) { + super.partitionPrivate_enforcePermission(); + enforceAdminUser(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); @@ -2351,6 +2367,8 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS) @Override public void partitionMixed(String diskId, int ratio) { + super.partitionMixed_enforcePermission(); + enforceAdminUser(); final CountDownLatch latch = findOrCreateDiskScanLatch(diskId); @@ -2366,6 +2384,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void setVolumeNickname(String fsUuid, String nickname) { + super.setVolumeNickname_enforcePermission(); + Objects.requireNonNull(fsUuid); synchronized (mLock) { final VolumeRecord rec = mRecords.get(fsUuid); @@ -2379,6 +2399,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void setVolumeUserFlags(String fsUuid, int flags, int mask) { + super.setVolumeUserFlags_enforcePermission(); + Objects.requireNonNull(fsUuid); synchronized (mLock) { final VolumeRecord rec = mRecords.get(fsUuid); @@ -2392,6 +2414,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void forgetVolume(String fsUuid) { + super.forgetVolume_enforcePermission(); + Objects.requireNonNull(fsUuid); synchronized (mLock) { @@ -2416,6 +2440,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void forgetAllVolumes() { + super.forgetAllVolumes_enforcePermission(); + synchronized (mLock) { for (int i = 0; i < mRecords.size(); i++) { final String fsUuid = mRecords.keyAt(i); @@ -2448,6 +2474,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void fstrim(int flags, IVoldTaskListener listener) { + super.fstrim_enforcePermission(); + try { // Block based checkpoint process runs fstrim. So, if checkpoint is in progress // (first boot after OTA), We skip idle maintenance and make sure the last @@ -2742,6 +2770,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void setDebugFlags(int flags, int mask) { + super.setDebugFlags_enforcePermission(); + if ((mask & (StorageManager.DEBUG_ADOPTABLE_FORCE_ON | StorageManager.DEBUG_ADOPTABLE_FORCE_OFF)) != 0) { final String value; @@ -2812,6 +2842,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) { + super.setPrimaryStorageUuid_enforcePermission(); + final VolumeInfo from; final VolumeInfo to; @@ -3020,6 +3052,8 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public boolean needsCheckpoint() throws RemoteException { + super.needsCheckpoint_enforcePermission(); + return mVold.needsCheckpoint(); } @@ -3040,6 +3074,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void createUserKey(int userId, int serialNumber, boolean ephemeral) { + super.createUserKey_enforcePermission(); + try { mVold.createUserKey(userId, serialNumber, ephemeral); // New keys are always unlocked. @@ -3055,6 +3091,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void destroyUserKey(int userId) { + super.destroyUserKey_enforcePermission(); + try { mVold.destroyUserKey(userId); // Destroying a key also locks it. @@ -3070,6 +3108,8 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.STORAGE_INTERNAL) @Override public void setUserKeyProtection(@UserIdInt int userId, byte[] secret) throws RemoteException { + super.setUserKeyProtection_enforcePermission(); + mVold.setUserKeyProtection(userId, HexDump.toHexString(secret)); } @@ -3078,6 +3118,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void unlockUserKey(@UserIdInt int userId, int serialNumber, byte[] secret) throws RemoteException { + super.unlockUserKey_enforcePermission(); + if (StorageManager.isFileEncrypted()) { mVold.unlockUserKey(userId, serialNumber, HexDump.toHexString(secret)); } @@ -3090,6 +3132,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void lockUserKey(int userId) { // Do not lock user 0 data for headless system user + super.lockUserKey_enforcePermission(); + if (userId == UserHandle.USER_SYSTEM && UserManager.isHeadlessSystemUserMode()) { throw new IllegalArgumentException("Headless system user data cannot be locked.."); @@ -3153,6 +3197,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) { + super.prepareUserStorage_enforcePermission(); + try { prepareUserStorageInternal(volumeUuid, userId, serialNumber, flags); } catch (Exception e) { @@ -3196,6 +3242,8 @@ class StorageManagerService extends IStorageManager.Stub @Override public void destroyUserStorage(String volumeUuid, int userId, int flags) { + super.destroyUserStorage_enforcePermission(); + try { mVold.destroyUserStorage(volumeUuid, userId, flags); } catch (Exception e) { @@ -4247,6 +4295,8 @@ class StorageManagerService extends IStorageManager.Stub @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) @Override public int getExternalStorageMountMode(int uid, String packageName) { + super.getExternalStorageMountMode_enforcePermission(); + return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName); } diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java index 9323d9536faa..7f24c52ccc6b 100644 --- a/services/core/java/com/android/server/SystemServerInitThreadPool.java +++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java @@ -32,6 +32,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -192,10 +193,12 @@ public final class SystemServerInitThreadPool implements Dumpable { private static void dumpStackTraces() { final ArrayList<Integer> pids = new ArrayList<>(); pids.add(Process.myPid()); - ActivityManagerService.dumpStackTraces(pids, /* processCpuTracker= */null, - /* lastPids= */null, Watchdog.getInterestingNativePids(), + ActivityManagerService.dumpStackTraces(pids, + /* processCpuTracker= */null, /* lastPids= */null, + CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()), /* logExceptionCreatingFile= */null, /* subject= */null, - /* criticalEventSection= */null, /* latencyTracker= */null); + /* criticalEventSection= */null, Runnable::run, + /* latencyTracker= */null); } @Override diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index b00dec00c030..6eeb9065cd58 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -49,7 +49,7 @@ import android.util.Dumpable; import android.util.EventLog; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.os.BackgroundThread; import com.android.internal.os.ProcessCpuTracker; @@ -75,6 +75,7 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; /** @@ -894,8 +895,9 @@ public class Watchdog implements Dumpable { ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false); StringWriter tracesFileException = new StringWriter(); final File stack = ActivityManagerService.dumpStackTraces( - pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(), - tracesFileException, subject, criticalEvents, /* latencyTracker= */null); + pids, processCpuTracker, new SparseBooleanArray(), + CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException, + subject, criticalEvents, Runnable::run, /* latencyTracker= */null); // Give some extra time to make sure the stack traces get written. // The system's been hanging for a whlie, another second or two won't hurt much. SystemClock.sleep(5000); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3032f17c2597..a17b5e273127 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -332,6 +332,7 @@ import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -467,10 +468,15 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; +import java.util.function.Supplier; public class ActivityManagerService extends IActivityManager.Stub implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock { @@ -3426,14 +3432,15 @@ public class ActivityManagerService extends IActivityManager.Stub * @param lastPids of dalvik VM processes to dump stack traces for last * @param nativePids optional list of native pids to dump stack crawls * @param logExceptionCreatingFile optional writer to which we log errors creating the file + * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on * @param latencyTracker the latency tracker instance of the current ANR. */ public static File dumpStackTraces(ArrayList<Integer> firstPids, - ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, - ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile, - AnrLatencyTracker latencyTracker) { - return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids, - logExceptionCreatingFile, null, null, null, latencyTracker); + ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, + Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, + @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { + return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, + logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker); } /** @@ -3444,64 +3451,44 @@ public class ActivityManagerService extends IActivityManager.Stub * @param logExceptionCreatingFile optional writer to which we log errors creating the file * @param subject optional line related to the error * @param criticalEventSection optional lines containing recent critical events. + * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on * @param latencyTracker the latency tracker instance of the current ANR. */ public static File dumpStackTraces(ArrayList<Integer> firstPids, - ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, - ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile, - String subject, String criticalEventSection, AnrLatencyTracker latencyTracker) { - return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids, - logExceptionCreatingFile, null, subject, criticalEventSection, latencyTracker); + ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, + Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, + String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor, + AnrLatencyTracker latencyTracker) { + return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture, + logExceptionCreatingFile, null, subject, criticalEventSection, + auxiliaryTaskExecutor, latencyTracker); } /** - * @param firstPidOffsets Optional, when it's set, it receives the start/end offset + * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset * of the very first pid to be dumped. */ /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids, - ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, - ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile, - long[] firstPidOffsets, String subject, String criticalEventSection, - AnrLatencyTracker latencyTracker) { + ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids, + Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile, + AtomicLong firstPidEndOffset, String subject, String criticalEventSection, + @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) { try { + if (latencyTracker != null) { latencyTracker.dumpStackTracesStarted(); } - ArrayList<Integer> extraPids = null; - Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids); + Slog.i(TAG, "dumpStackTraces pids=" + lastPids); // Measure CPU usage as soon as we're called in order to get a realistic sampling // of the top users at the time of the request. - if (processCpuTracker != null) { - if (latencyTracker != null) { - latencyTracker.processCpuTrackerMethodsCalled(); - } - processCpuTracker.init(); - try { - Thread.sleep(200); - } catch (InterruptedException ignored) { - } - - processCpuTracker.update(); - - // We'll take the stack crawls of just the top apps using CPU. - final int workingStatsNumber = processCpuTracker.countWorkingStats(); - extraPids = new ArrayList<>(); - for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) { - ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); - if (lastPids.indexOfKey(stats.pid) >= 0) { - if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); - - extraPids.add(stats.pid); - } else { - Slog.i(TAG, "Skipping next CPU consuming process, not a java proc: " - + stats.pid); - } - } - if (latencyTracker != null) { - latencyTracker.processCpuTrackerMethodsReturned(); - } + Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null + ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null; + Future<ArrayList<Integer>> extraPidsFuture = null; + if (extraPidsSupplier != null) { + extraPidsFuture = + CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor); } final File tracesDir = new File(ANR_TRACE_DIR); @@ -3534,15 +3521,11 @@ public class ActivityManagerService extends IActivityManager.Stub + (criticalEventSection != null ? criticalEventSection : "")); } - Pair<Long, Long> offsets = dumpStackTraces( - tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, latencyTracker); - if (firstPidOffsets != null) { - if (offsets == null) { - firstPidOffsets[0] = firstPidOffsets[1] = -1; - } else { - firstPidOffsets[0] = offsets.first; // Start offset to the ANR trace file - firstPidOffsets[1] = offsets.second; // End offset to the ANR trace file - } + long firstPidEndPos = dumpStackTraces( + tracesFile.getAbsolutePath(), firstPids, nativePidsFuture, + extraPidsFuture, latencyTracker); + if (firstPidEndOffset != null) { + firstPidEndOffset.set(firstPidEndPos); } return tracesFile; @@ -3558,6 +3541,42 @@ public class ActivityManagerService extends IActivityManager.Stub private static SimpleDateFormat sAnrFileDateFormat; static final String ANR_FILE_PREFIX = "anr_"; + private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker, + SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) { + if (latencyTracker != null) { + latencyTracker.processCpuTrackerMethodsCalled(); + } + ArrayList<Integer> extraPids = new ArrayList<>(); + processCpuTracker.init(); + try { + Thread.sleep(200); + } catch (InterruptedException ignored) { + } + + processCpuTracker.update(); + + // We'll take the stack crawls of just the top apps using CPU. + final int workingStatsNumber = processCpuTracker.countWorkingStats(); + for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) { + ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); + if (lastPids.indexOfKey(stats.pid) >= 0) { + if (DEBUG_ANR) { + Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); + } + + extraPids.add(stats.pid); + } else { + Slog.i(TAG, + "Skipping next CPU consuming process, not a java proc: " + + stats.pid); + } + } + if (latencyTracker != null) { + latencyTracker.processCpuTrackerMethodsReturned(); + } + return extraPids; + } + private static synchronized File createAnrDumpFile(File tracesDir) throws IOException { if (sAnrFileDateFormat == null) { sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS"); @@ -3661,11 +3680,11 @@ public class ActivityManagerService extends IActivityManager.Stub /** - * @return The start/end offset of the trace of the very first PID + * @return The end offset of the trace of the very first PID */ - public static Pair<Long, Long> dumpStackTraces(String tracesFile, - ArrayList<Integer> firstPids, ArrayList<Integer> nativePids, - ArrayList<Integer> extraPids, AnrLatencyTracker latencyTracker) { + public static long dumpStackTraces(String tracesFile, + ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture, + Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) { Slog.i(TAG, "Dumping to " + tracesFile); @@ -3679,7 +3698,6 @@ public class ActivityManagerService extends IActivityManager.Stub // As applications are usually interested with the ANR stack traces, but we can't share with // them the stack traces other than their own stacks. So after the very first PID is // dumped, remember the current file size. - long firstPidStart = -1; long firstPidEnd = -1; // First collect all of the stacks of the most important pids. @@ -3687,16 +3705,12 @@ public class ActivityManagerService extends IActivityManager.Stub if (latencyTracker != null) { latencyTracker.dumpingFirstPidsStarted(); } + int num = firstPids.size(); for (int i = 0; i < num; i++) { final int pid = firstPids.get(i); // We don't copy ANR traces from the system_server intentionally. final boolean firstPid = i == 0 && MY_PID != pid; - File tf = null; - if (firstPid) { - tf = new File(tracesFile); - firstPidStart = tf.exists() ? tf.length() : 0; - } if (latencyTracker != null) { latencyTracker.dumpingPidStarted(pid); } @@ -3712,11 +3726,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (remainingTime <= 0) { Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid + "); deadline exceeded."); - return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null; + return firstPidEnd; } if (firstPid) { - firstPidEnd = tf.length(); + firstPidEnd = new File(tracesFile).length(); // Full latency dump if (latencyTracker != null) { appendtoANRFile(tracesFile, @@ -3733,6 +3747,10 @@ public class ActivityManagerService extends IActivityManager.Stub } // Next collect the stacks of the native pids + ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids"); + + Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids); + if (nativePids != null) { if (latencyTracker != null) { latencyTracker.dumpingNativePidsStarted(); @@ -3755,7 +3773,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (remainingTime <= 0) { Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid + "); deadline exceeded."); - return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null; + return firstPidEnd; } if (DEBUG_ANR) { @@ -3768,6 +3786,19 @@ public class ActivityManagerService extends IActivityManager.Stub } // Lastly, dump stacks for all extra PIDs from the CPU tracker. + ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids"); + + if (extraPidsFuture != null) { + try { + extraPids = extraPidsFuture.get(); + } catch (ExecutionException e) { + Slog.w(TAG, "Failed to collect extra pids", e.getCause()); + } catch (InterruptedException e) { + Slog.w(TAG, "Interrupted while collecting extra pids", e); + } + } + Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids); + if (extraPids != null) { if (latencyTracker != null) { latencyTracker.dumpingExtraPidsStarted(); @@ -3785,7 +3816,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (remainingTime <= 0) { Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid + "); deadline exceeded."); - return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null; + return firstPidEnd; } if (DEBUG_ANR) { @@ -3800,7 +3831,25 @@ public class ActivityManagerService extends IActivityManager.Stub appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n"); Slog.i(TAG, "Done dumping"); - return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null; + return firstPidEnd; + } + + private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture, + String logName) { + + ArrayList<Integer> pids = null; + + if (pidsFuture == null) { + return pids; + } + try { + pids = pidsFuture.get(); + } catch (ExecutionException e) { + Slog.w(TAG, "Failed to collect " + logName, e.getCause()); + } catch (InterruptedException e) { + Slog.w(TAG, "Interrupted while collecting " + logName , e); + } + return pids; } @Override @@ -13836,6 +13885,25 @@ public class ActivityManagerService extends IActivityManager.Stub } } + // Apply permission policy around the use of specific broadcast options + void enforceBroadcastOptionPermissionsInternal(@Nullable Bundle options, int callingUid) { + if (options != null && callingUid != Process.SYSTEM_UID) { + if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) { + if (DEBUG_BROADCAST_LIGHT) { + Slog.w(TAG, "Non-system caller " + callingUid + + " may not flag broadcast as alarm"); + } + throw new SecurityException( + "Non-system callers may not flag broadcasts as alarm"); + } + if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) { + enforceCallingPermission( + android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE, + "setInteractiveBroadcast"); + } + } + } + @GuardedBy("this") final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage, String callerFeatureId, Intent intent, String resolvedType, @@ -13865,6 +13933,29 @@ public class ActivityManagerService extends IActivityManager.Stub @Nullable IBinder backgroundActivityStartsToken, @Nullable int[] broadcastAllowList, @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) { + final int cookie = BroadcastQueue.traceBegin("broadcastIntentLockedTraced"); + final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId, + intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras, + requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions, + ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId, + allowBackgroundActivityStarts, backgroundActivityStartsToken, broadcastAllowList, + filterExtrasForReceiver); + BroadcastQueue.traceEnd(cookie); + return res; + } + + @GuardedBy("this") + final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage, + @Nullable String callerFeatureId, Intent intent, String resolvedType, + ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData, + Bundle resultExtras, String[] requiredPermissions, + String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions, + boolean ordered, boolean sticky, int callingPid, int callingUid, + int realCallingUid, int realCallingPid, int userId, + boolean allowBackgroundActivityStarts, + @Nullable IBinder backgroundActivityStartsToken, + @Nullable int[] broadcastAllowList, + @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) { // Ensure all internal loopers are registered for idle checks BroadcastLoopers.addMyLooper(); @@ -14416,6 +14507,7 @@ public class ActivityManagerService extends IActivityManager.Stub } // Figure out who all will receive this broadcast. + final int cookie = BroadcastQueue.traceBegin("queryReceivers"); List receivers = null; List<BroadcastFilter> registeredReceivers = null; // Need to resolve the intent to interested receivers... @@ -14446,6 +14538,7 @@ public class ActivityManagerService extends IActivityManager.Stub resolvedType, false /*defaultOnly*/, userId); } } + BroadcastQueue.traceEnd(cookie); final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0; @@ -14630,6 +14723,15 @@ public class ActivityManagerService extends IActivityManager.Stub mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage); } + final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) { + final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null; + final String callerPackage = info != null ? info.packageName : original.callerPackage; + if (callerPackage != null) { + mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT, + original.callingUid, 0, callerPackage).sendToTarget(); + } + } + final Intent verifyBroadcastLocked(Intent intent) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { @@ -14703,19 +14805,8 @@ public class ActivityManagerService extends IActivityManager.Stub // We're delivering the result to the caller final ProcessRecord resultToApp = callerApp; - // Non-system callers can't declare that a broadcast is alarm-related. - // The PendingIntent invocation case is handled in PendingIntentRecord. - if (bOptions != null && callingUid != SYSTEM_UID) { - if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST) - || bOptions.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) { - if (DEBUG_BROADCAST) { - Slog.w(TAG, "Non-system caller " + callingUid - + " may not flag broadcast as alarm or interactive"); - } - throw new SecurityException( - "Non-system callers may not flag broadcasts as alarm or interactive"); - } - } + // Permission regimes around sender-supplied broadcast options. + enforceBroadcastOptionPermissionsInternal(bOptions, callingUid); final long origId = Binder.clearCallingIdentity(); try { @@ -16771,7 +16862,6 @@ public class ActivityManagerService extends IActivityManager.Stub mAtmInternal.onUserStopped(userId); // Clean up various services by removing the user mBatteryStatsService.onUserRemoved(userId); - mUserController.onUserRemoved(userId); } @Override @@ -16910,6 +17000,11 @@ public class ActivityManagerService extends IActivityManager.Stub return mEnableModernQueue; } + @Override + public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) { + enforceBroadcastOptionPermissionsInternal(options, callingUid); + } + /** * Returns package name by pid. */ diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java index 6de4118f8fbb..71c80ea4f045 100644 --- a/services/core/java/com/android/server/am/AnrHelper.java +++ b/services/core/java/com/android/server/am/AnrHelper.java @@ -25,10 +25,15 @@ import android.os.Trace; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.TimeoutRecord; import com.android.server.wm.WindowProcessController; import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -51,6 +56,14 @@ class AnrHelper { */ private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2); + /** + * The keep alive time for the threads in the helper threadpool executor + */ + private static final int AUX_THREAD_KEEP_ALIVE_SECOND = 10; + + private static final ThreadFactory sDefaultThreadFactory = r -> + new Thread(r, "AnrAuxiliaryTaskExecutor"); + @GuardedBy("mAnrRecords") private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>(); private final AtomicBoolean mRunning = new AtomicBoolean(false); @@ -66,8 +79,18 @@ class AnrHelper { @GuardedBy("mAnrRecords") private int mProcessingPid = -1; + private final ExecutorService mAuxiliaryTaskExecutor; + AnrHelper(final ActivityManagerService service) { + this(service, new ThreadPoolExecutor(/* corePoolSize= */ 0, /* maximumPoolSize= */ 1, + /* keepAliveTime= */ AUX_THREAD_KEEP_ALIVE_SECOND, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(), sDefaultThreadFactory)); + } + + @VisibleForTesting + AnrHelper(ActivityManagerService service, ExecutorService auxExecutor) { mService = service; + mAuxiliaryTaskExecutor = auxExecutor; } void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) { @@ -108,7 +131,8 @@ class AnrHelper { } timeoutRecord.mLatencyTracker.anrRecordPlacingOnQueueWithSize(mAnrRecords.size()); mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo, - parentShortComponentName, parentProcess, aboveSystem, timeoutRecord)); + parentShortComponentName, parentProcess, aboveSystem, + mAuxiliaryTaskExecutor, timeoutRecord)); } startAnrConsumerIfNeeded(); } finally { @@ -204,11 +228,12 @@ class AnrHelper { final ApplicationInfo mAppInfo; final WindowProcessController mParentProcess; final boolean mAboveSystem; + final ExecutorService mAuxiliaryTaskExecutor; final long mTimestamp = SystemClock.uptimeMillis(); AnrRecord(ProcessRecord anrProcess, String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, - TimeoutRecord timeoutRecord) { + ExecutorService auxiliaryTaskExecutor, TimeoutRecord timeoutRecord) { mApp = anrProcess; mPid = anrProcess.mPid; mActivityShortComponentName = activityShortComponentName; @@ -217,6 +242,7 @@ class AnrHelper { mAppInfo = aInfo; mParentProcess = parentProcess; mAboveSystem = aboveSystem; + mAuxiliaryTaskExecutor = auxiliaryTaskExecutor; } void appNotResponding(boolean onlyDumpSelf) { @@ -224,7 +250,7 @@ class AnrHelper { mTimeoutRecord.mLatencyTracker.anrProcessingStarted(); mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo, mParentShortComponentName, mParentProcess, mAboveSystem, - mTimeoutRecord, onlyDumpSelf); + mTimeoutRecord, mAuxiliaryTaskExecutor, onlyDumpSelf); } finally { mTimeoutRecord.mLatencyTracker.anrProcessingEnded(); } diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java index bebb48473fc3..b828720c9162 100644 --- a/services/core/java/com/android/server/am/BroadcastLoopers.java +++ b/services/core/java/com/android/server/am/BroadcastLoopers.java @@ -25,6 +25,8 @@ import android.os.SystemClock; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; import java.util.Objects; import java.util.concurrent.CountDownLatch; @@ -37,6 +39,7 @@ import java.util.concurrent.CountDownLatch; public class BroadcastLoopers { private static final String TAG = "BroadcastLoopers"; + @GuardedBy("sLoopers") private static final ArraySet<Looper> sLoopers = new ArraySet<>(); /** diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index f7d24e9b8b4e..c994f13d4f73 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -114,7 +114,14 @@ class BroadcastProcessQueue { * dispatched to this process, in the same representation as * {@link #mPending}. */ - private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>(); + private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>(4); + + /** + * Ordered collection of "offload" broadcasts that are waiting to be + * dispatched to this process, in the same representation as + * {@link #mPending}. + */ + private final ArrayDeque<SomeArgs> mPendingOffload = new ArrayDeque<>(4); /** * Broadcast actively being dispatched to this process. @@ -128,14 +135,6 @@ class BroadcastProcessQueue { private int mActiveIndex; /** - * When defined, the receiver actively being dispatched into this process - * was considered "blocked" until at least the given count of other - * receivers have reached a terminal state; typically used for ordered - * broadcasts and priority traunches. - */ - private int mActiveBlockedUntilTerminalCount; - - /** * Count of {@link #mActive} broadcasts that have been dispatched since this * queue was last idle. */ @@ -148,8 +147,7 @@ class BroadcastProcessQueue { private boolean mActiveViaColdStart; /** - * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of - * these various flavors. + * Count of pending broadcasts of these various flavors. */ private int mCountForeground; private int mCountOrdered; @@ -177,6 +175,16 @@ class BroadcastProcessQueue { this.uid = uid; } + private @NonNull ArrayDeque<SomeArgs> getQueueForBroadcast(@NonNull BroadcastRecord record) { + if (record.isUrgent()) { + return mPendingUrgent; + } else if (record.isOffload()) { + return mPendingOffload; + } else { + return mPending; + } + } + /** * Enqueue the given broadcast to be dispatched to this process at some * future point in time. The target receiver is indicated by the given index @@ -190,13 +198,11 @@ class BroadcastProcessQueue { * given count of other receivers have reached a terminal state; typically * used for ordered broadcasts and priority traunches. */ - public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex, - int blockedUntilTerminalCount) { + public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) { if (record.isReplacePending()) { - boolean didReplace = replaceBroadcastInQueue(mPending, - record, recordIndex, blockedUntilTerminalCount) - || replaceBroadcastInQueue(mPendingUrgent, - record, recordIndex, blockedUntilTerminalCount); + boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex) + || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex) + || replaceBroadcastInQueue(mPendingOffload, record, recordIndex); if (didReplace) { return; } @@ -207,14 +213,12 @@ class BroadcastProcessQueue { SomeArgs newBroadcastArgs = SomeArgs.obtain(); newBroadcastArgs.arg1 = record; newBroadcastArgs.argi1 = recordIndex; - newBroadcastArgs.argi2 = blockedUntilTerminalCount; // Cross-broadcast prioritization policy: some broadcasts might warrant being // issued ahead of others that are already pending, for example if this new // broadcast is in a different delivery class or is tied to a direct user interaction // with implicit responsiveness expectations. - final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending; - queue.addLast(newBroadcastArgs); + getQueueForBroadcast(record).addLast(newBroadcastArgs); onBroadcastEnqueued(record, recordIndex); } @@ -227,7 +231,7 @@ class BroadcastProcessQueue { * {@code false} otherwise. */ private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue, - @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) { + @NonNull BroadcastRecord record, int recordIndex) { final Iterator<SomeArgs> it = queue.descendingIterator(); final Object receiver = record.receivers.get(recordIndex); while (it.hasNext()) { @@ -242,7 +246,6 @@ class BroadcastProcessQueue { // Exact match found; perform in-place swap args.arg1 = record; args.argi1 = recordIndex; - args.argi2 = blockedUntilTerminalCount; onBroadcastDequeued(testRecord, testRecordIndex); onBroadcastEnqueued(record, recordIndex); return true; @@ -279,10 +282,13 @@ class BroadcastProcessQueue { */ public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer, boolean andRemove) { - boolean didSomething = forEachMatchingBroadcastInQueue(mPending, + boolean didSomething = false; + didSomething |= forEachMatchingBroadcastInQueue(mPending, predicate, consumer, andRemove); didSomething |= forEachMatchingBroadcastInQueue(mPendingUrgent, predicate, consumer, andRemove); + didSomething |= forEachMatchingBroadcastInQueue(mPendingOffload, + predicate, consumer, andRemove); return didSomething; } @@ -391,7 +397,6 @@ class BroadcastProcessQueue { final SomeArgs next = removeNextBroadcast(); mActive = (BroadcastRecord) next.arg1; mActiveIndex = next.argi1; - mActiveBlockedUntilTerminalCount = next.argi2; mActiveCountSinceIdle++; mActiveViaColdStart = false; next.recycle(); @@ -404,7 +409,6 @@ class BroadcastProcessQueue { public void makeActiveIdle() { mActive = null; mActiveIndex = 0; - mActiveBlockedUntilTerminalCount = -1; mActiveCountSinceIdle = 0; mActiveViaColdStart = false; invalidateRunnableAt(); @@ -516,7 +520,7 @@ class BroadcastProcessQueue { } public boolean isEmpty() { - return mPending.isEmpty() && mPendingUrgent.isEmpty(); + return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty(); } public boolean isActive() { @@ -537,6 +541,8 @@ class BroadcastProcessQueue { return mPendingUrgent; } else if (!mPending.isEmpty()) { return mPending; + } else if (!mPendingOffload.isEmpty()) { + return mPendingOffload; } return null; } @@ -581,12 +587,15 @@ class BroadcastProcessQueue { } final SomeArgs next = mPending.peekFirst(); final SomeArgs nextUrgent = mPendingUrgent.peekFirst(); + final SomeArgs nextOffload = mPendingOffload.peekFirst(); // Empty queue is past any barrier - final boolean nextLater = next == null + final boolean nextLater = (next == null) || ((BroadcastRecord) next.arg1).enqueueTime > barrierTime; - final boolean nextUrgentLater = nextUrgent == null + final boolean nextUrgentLater = (nextUrgent == null) || ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime; - return nextLater && nextUrgentLater; + final boolean nextOffloadLater = (nextOffload == null) + || ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime; + return nextLater && nextUrgentLater && nextOffloadLater; } public boolean isRunnable() { @@ -680,7 +689,7 @@ class BroadcastProcessQueue { if (next != null) { final BroadcastRecord r = (BroadcastRecord) next.arg1; final int index = next.argi1; - final int blockedUntilTerminalCount = next.argi2; + final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index]; final long runnableAt = r.enqueueTime; // We might be blocked waiting for other receivers to finish, @@ -726,8 +735,9 @@ class BroadcastProcessQueue { // If we have too many broadcasts pending, bypass any delays that // might have been applied above to aid draining - if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) { - mRunnableAt = runnableAt; + if (mPending.size() + mPendingUrgent.size() + + mPendingOffload.size() >= constants.MAX_PENDING_BROADCASTS) { + mRunnableAt = Math.min(mRunnableAt, runnableAt); mRunnableAtReason = REASON_MAX_PENDING; } } else { @@ -845,23 +855,27 @@ class BroadcastProcessQueue { pw.println(); pw.increaseIndent(); if (mActive != null) { - dumpRecord(now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount); + dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex); } for (SomeArgs args : mPendingUrgent) { final BroadcastRecord r = (BroadcastRecord) args.arg1; - dumpRecord(now, pw, r, args.argi1, args.argi2); + dumpRecord("URGENT", now, pw, r, args.argi1); } for (SomeArgs args : mPending) { final BroadcastRecord r = (BroadcastRecord) args.arg1; - dumpRecord(now, pw, r, args.argi1, args.argi2); + dumpRecord(null, now, pw, r, args.argi1); + } + for (SomeArgs args : mPendingOffload) { + final BroadcastRecord r = (BroadcastRecord) args.arg1; + dumpRecord("OFFLOAD", now, pw, r, args.argi1); } pw.decreaseIndent(); pw.println(); } @NeverCompile - private void dumpRecord(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw, - @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) { + private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now, + @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) { TimeUtils.formatDuration(record.enqueueTime, now, pw); pw.print(' '); pw.println(record.toShortString()); @@ -872,6 +886,10 @@ class BroadcastProcessQueue { pw.print(" at "); TimeUtils.formatDuration(record.scheduledTime[recordIndex], now, pw); } + if (flavor != null) { + pw.print(' '); + pw.print(flavor); + } final Object receiver = record.receivers.get(recordIndex); if (receiver instanceof BroadcastFilter) { final BroadcastFilter filter = (BroadcastFilter) receiver; @@ -883,6 +901,7 @@ class BroadcastProcessQueue { pw.print(info.activityInfo.name); } pw.println(); + final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex]; if (blockedUntilTerminalCount != -1) { pw.print(" blocked until "); pw.print(blockedUntilTerminalCount); diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 1e172fc92f40..153ad1ec1b52 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.DropBoxManager; import android.os.Handler; +import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -76,6 +77,30 @@ public abstract class BroadcastQueue { } } + static void checkState(boolean expression, @NonNull String msg) { + if (!expression) { + throw new IllegalStateException(msg); + } + } + + static void checkStateWtf(boolean expression, @NonNull String msg) { + if (!expression) { + Slog.wtf(TAG, new IllegalStateException(msg)); + } + } + + static int traceBegin(@NonNull String methodName) { + final int cookie = methodName.hashCode(); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, + TAG, methodName, cookie); + return cookie; + } + + static void traceEnd(int cookie) { + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, + TAG, cookie); + } + @Override public String toString() { return mQueueName; diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index ffc54d90aec6..454b28402ced 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -1690,13 +1690,7 @@ public class BroadcastQueueImpl extends BroadcastQueue { System.identityHashCode(original)); } - final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null; - final String callerPackage = info != null ? info.packageName : original.callerPackage; - if (callerPackage != null) { - mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT, - original.callingUid, 0, callerPackage).sendToTarget(); - } - + mService.notifyBroadcastFinishedLocked(original); mHistory.addBroadcastToHistoryLocked(original); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 9e9eb71db4e5..c3839a99ba52 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -25,14 +25,12 @@ import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVE import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM; import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST; import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME; -import static com.android.internal.util.Preconditions.checkState; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList; import static com.android.server.am.BroadcastProcessQueue.reasonToString; import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList; import static com.android.server.am.BroadcastRecord.deliveryStateToString; import static com.android.server.am.BroadcastRecord.getReceiverPackageName; -import static com.android.server.am.BroadcastRecord.getReceiverPriority; import static com.android.server.am.BroadcastRecord.getReceiverProcessName; import static com.android.server.am.BroadcastRecord.getReceiverUid; import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; @@ -64,7 +62,6 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.Trace; import android.os.UserHandle; import android.text.format.DateUtils; import android.util.IndentingPrintWriter; @@ -144,14 +141,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES]; } - // TODO: add support for replacing pending broadcasts - // TODO: add support for merging pending broadcasts - - // TODO: consider reordering foreground broadcasts within queue - - // TODO: pause queues when background services are running - // TODO: pause queues when processes are frozen - /** * Map from UID to per-process broadcast queues. If a UID hosts more than * one process, each additional process is stored as a linked list using @@ -222,12 +211,22 @@ class BroadcastQueueModernImpl extends BroadcastQueue { private static final int MSG_DELIVERY_TIMEOUT_HARD = 3; private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4; private static final int MSG_CHECK_HEALTH = 5; + private static final int MSG_FINISH_RECEIVER = 6; private void enqueueUpdateRunningList() { mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST); mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST); } + private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue, + @DeliveryState int deliveryState, @NonNull String reason) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = queue; + args.argi1 = deliveryState; + args.arg2 = reason; + mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args)); + } + private final Handler mLocalHandler; private final Handler.Callback mLocalCallback = (msg) -> { @@ -266,6 +265,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } return true; } + case MSG_FINISH_RECEIVER: { + synchronized (mService) { + final SomeArgs args = (SomeArgs) msg.obj; + final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1; + final int deliveryState = args.argi1; + final String reason = (String) args.arg2; + args.recycle(); + finishReceiverLocked(queue, deliveryState, reason); + } + return true; + } } return false; }; @@ -349,7 +359,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { int avail = mRunning.length - getRunningSize(); if (avail == 0) return; - final int cookie = traceBegin(TAG, "updateRunningList"); + final int cookie = traceBegin("updateRunningList"); final long now = SystemClock.uptimeMillis(); // If someone is waiting for a state, everything is runnable now @@ -412,21 +422,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]"; queue.runningOomAdjusted = queue.isPendingManifest(); + // If already warm, we can make OOM adjust request immediately; + // otherwise we need to wait until process becomes warm + if (processWarm) { + notifyStartedRunning(queue); + updateOomAdj |= queue.runningOomAdjusted; + } + // If we're already warm, schedule next pending broadcast now; // otherwise we'll wait for the cold start to circle back around queue.makeActiveNextPending(); if (processWarm) { queue.traceProcessRunningBegin(); - notifyStartedRunning(queue); scheduleReceiverWarmLocked(queue); } else { queue.traceProcessStartingBegin(); scheduleReceiverColdLocked(queue); } - // Only kick off an OOM adjustment pass if needed - updateOomAdj |= queue.runningOomAdjusted; - // Move to considering next runnable queue queue = nextQueue; } @@ -446,7 +459,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { }); } - traceEnd(TAG, cookie); + traceEnd(cookie); } @Override @@ -464,9 +477,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // now; dispatch its next broadcast and clear the slot mRunningColdStart = null; + // Now that we're running warm, we can finally request that OOM + // adjust we've been waiting for + notifyStartedRunning(queue); + mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER); + queue.traceProcessEnd(); queue.traceProcessRunningBegin(); - notifyStartedRunning(queue); scheduleReceiverWarmLocked(queue); // We might be willing to kick off another cold start @@ -509,7 +526,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (queue != null) { // If queue was running a broadcast, fail it if (queue.isActive()) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); + finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE, + "onApplicationCleanupLocked"); } // Skip any pending registered receivers, since the old process @@ -537,6 +555,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) { if (DEBUG_BROADCAST) logv("Enqueuing " + r + " for " + r.receivers.size() + " receivers"); + final int cookie = traceBegin("enqueueBroadcast"); r.applySingletonPolicy(mService); final IntentFilter removeMatchingFilter = (r.options != null) @@ -568,36 +587,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.enqueueRealTime = SystemClock.elapsedRealtime(); r.enqueueClockTime = System.currentTimeMillis(); - int lastPriority = 0; - int lastPriorityIndex = 0; - for (int i = 0; i < r.receivers.size(); i++) { final Object receiver = r.receivers.get(i); final BroadcastProcessQueue queue = getOrCreateProcessQueue( getReceiverProcessName(receiver), getReceiverUid(receiver)); - - final int blockedUntilTerminalCount; - if (r.ordered) { - // When sending an ordered broadcast, we need to block this - // receiver until all previous receivers have terminated - blockedUntilTerminalCount = i; - } else if (r.prioritized) { - // When sending a prioritized broadcast, we only need to wait - // for the previous traunch of receivers to be terminated - final int thisPriority = getReceiverPriority(receiver); - if ((i == 0) || (thisPriority != lastPriority)) { - lastPriority = thisPriority; - lastPriorityIndex = i; - blockedUntilTerminalCount = i; - } else { - blockedUntilTerminalCount = lastPriorityIndex; - } - } else { - // Otherwise we don't need to block at all - blockedUntilTerminalCount = -1; - } - - queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount); + queue.enqueueOrReplaceBroadcast(r, i); updateRunnableList(queue); enqueueUpdateRunningList(); } @@ -606,6 +600,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (r.receivers.isEmpty()) { scheduleResultTo(r); } + + traceEnd(cookie); } private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) { @@ -661,7 +657,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // Ignore registered receivers from a previous PID if (receiver instanceof BroadcastFilter) { mRunningColdStart = null; - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, + "BroadcastFilter for cold app"); return; } @@ -683,7 +680,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { hostingRecord, zygotePolicyFlags, allowWhileBooting, false); if (queue.app == null) { mRunningColdStart = null; - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, + "startProcessLocked failed"); return; } } @@ -714,33 +712,37 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // If someone already finished this broadcast, finish immediately final int oldDeliveryState = getDeliveryState(r, index); if (isDeliveryStateTerminal(oldDeliveryState)) { - finishReceiverLocked(queue, oldDeliveryState); + enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state"); return; } // Consider additional cases where we'd want to finish immediately if (app.isInFullBackup()) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup"); return; } if (mSkipPolicy.shouldSkip(r, receiver)) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy"); return; } final Intent receiverIntent = r.getReceiverIntent(receiver); if (receiverIntent == null) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent"); return; } // Ignore registered receivers from a previous PID if ((receiver instanceof BroadcastFilter) && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, + "BroadcastFilter for mismatched PID"); return; } - if (mService.mProcessesReady && !r.timeoutExempt) { + // Skip ANR tracking early during boot, when requested, or when we + // immediately assume delivery success + final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered; + if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) { queue.lastCpuDelayTime = queue.app.getCpuDelayTime(); final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT; @@ -768,7 +770,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app); - setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED); + setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED, + "scheduleReceiverWarmLocked"); final IApplicationThread thread = app.getOnewayThread(); if (thread != null) { @@ -782,8 +785,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // TODO: consider making registered receivers of unordered // broadcasts report results to detect ANRs - if (!r.ordered) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED); + if (assumeDelivered) { + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_DELIVERED, + "assuming delivered"); } } else { notifyScheduleReceiver(app, r, (ResolveInfo) receiver); @@ -797,10 +801,11 @@ class BroadcastQueueModernImpl extends BroadcastQueue { logw(msg); app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null); app.setKilled(true); - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app"); } } else { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE); + enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, + "missing IApplicationThread"); } } @@ -809,9 +814,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * ordered broadcast; assumes the sender is still a warm process. */ private void scheduleResultTo(@NonNull BroadcastRecord r) { - if ((r.resultToApp == null) || (r.resultTo == null)) return; + if (r.resultTo == null) return; final ProcessRecord app = r.resultToApp; - final IApplicationThread thread = app.getOnewayThread(); + final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null; if (thread != null) { mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily( app, OOM_ADJ_REASON_FINISH_RECEIVER); @@ -844,7 +849,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) { - finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT); + finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT, + "deliveryTimeoutHardLocked"); } @Override @@ -871,24 +877,28 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (r.resultAbort) { for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) { setDeliveryState(null, null, r, i, r.receivers.get(i), - BroadcastRecord.DELIVERY_SKIPPED); + BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); } } } - return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED); + return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); } private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue, - @DeliveryState int deliveryState) { - checkState(queue.isActive(), "isActive"); + @DeliveryState int deliveryState, @NonNull String reason) { + if (!queue.isActive()) { + logw("Ignoring finish; no active broadcast for " + queue); + return false; + } + final int cookie = traceBegin("finishReceiver"); final ProcessRecord app = queue.app; final BroadcastRecord r = queue.getActive(); final int index = queue.getActiveIndex(); final Object receiver = r.receivers.get(index); - setDeliveryState(queue, app, r, index, receiver, deliveryState); + setDeliveryState(queue, app, r, index, receiver, deliveryState, reason); if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) { r.anrCount++; @@ -907,11 +917,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final boolean shouldRetire = (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS); + final boolean res; if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) { // We're on a roll; move onto the next broadcast for this process queue.makeActiveNextPending(); scheduleReceiverWarmLocked(queue); - return true; + res = true; } else { // We've drained running broadcasts; maybe move back to runnable queue.makeActiveIdle(); @@ -925,8 +936,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // Tell other OS components that app is not actively running, giving // a chance to update OOM adjustment notifyStoppedRunning(queue); - return false; + res = false; } + traceEnd(cookie); + return res; } /** @@ -935,7 +948,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { */ private void setDeliveryState(@Nullable BroadcastProcessQueue queue, @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index, - @NonNull Object receiver, @DeliveryState int newDeliveryState) { + @NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) { + final int cookie = traceBegin("setDeliveryState"); final int oldDeliveryState = getDeliveryState(r, index); // Only apply state when we haven't already reached a terminal state; @@ -963,7 +977,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { logw("Delivery state of " + r + " to " + receiver + " via " + app + " changed from " + deliveryStateToString(oldDeliveryState) + " to " - + deliveryStateToString(newDeliveryState)); + + deliveryStateToString(newDeliveryState) + " because " + reason); } r.terminalCount++; @@ -993,6 +1007,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { enqueueUpdateRunningList(); } } + + traceEnd(cookie); } private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) { @@ -1053,7 +1069,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * of it matching a predicate. */ private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> { - setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED); + setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED, + "mBroadcastConsumerSkip"); }; /** @@ -1061,7 +1078,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * cancelled, usually as a result of it matching a predicate. */ private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> { - setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED); + setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED, + "mBroadcastConsumerSkipAndCanceled"); r.resultCode = Activity.RESULT_CANCELED; r.resultData = null; r.resultExtras = null; @@ -1253,18 +1271,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } - private int traceBegin(String trackName, String methodName) { - final int cookie = methodName.hashCode(); - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - trackName, methodName, cookie); - return cookie; - } - - private void traceEnd(String trackName, int cookie) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, - trackName, cookie); - } - private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) { if (!queue.isProcessWarm()) { queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid)); @@ -1396,6 +1402,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final boolean recordFinished = (r.terminalCount == r.receivers.size()); if (recordFinished) { + mService.notifyBroadcastFinishedLocked(r); mHistory.addBroadcastToHistoryLocked(r); r.finishTime = SystemClock.uptimeMillis(); diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 2a3c8974361f..6ea2dee5b578 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -96,6 +96,7 @@ final class BroadcastRecord extends Binder { final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo final @DeliveryState int[] delivery; // delivery state of each receiver + final int[] blockedUntilTerminalCount; // blocked until count of each receiver @Nullable ProcessRecord resultToApp; // who receives final result if non-null @Nullable IIntentReceiver resultTo; // who receives final result if non-null boolean deferred; @@ -375,6 +376,7 @@ final class BroadcastRecord extends Binder { options = _options; receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS; delivery = new int[_receivers != null ? _receivers.size() : 0]; + blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized); scheduledTime = new long[delivery.length]; terminalTime = new long[delivery.length]; resultToApp = _resultToApp; @@ -385,7 +387,7 @@ final class BroadcastRecord extends Binder { ordered = _serialized; sticky = _sticky; initialSticky = _initialSticky; - prioritized = isPrioritized(receivers); + prioritized = isPrioritized(blockedUntilTerminalCount, _serialized); userId = _userId; nextReceiver = 0; state = IDLE; @@ -427,6 +429,7 @@ final class BroadcastRecord extends Binder { options = from.options; receivers = from.receivers; delivery = from.delivery; + blockedUntilTerminalCount = from.blockedUntilTerminalCount; scheduledTime = from.scheduledTime; terminalTime = from.terminalTime; resultToApp = from.resultToApp; @@ -617,6 +620,10 @@ final class BroadcastRecord extends Binder { return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0; } + boolean isOffload() { + return (intent.getFlags() & Intent.FLAG_RECEIVER_OFFLOAD) != 0; + } + /** * Core policy determination about this broadcast's delivery prioritization */ @@ -686,23 +693,61 @@ final class BroadcastRecord extends Binder { } /** - * Return if given receivers list has more than one traunch of priorities. + * Determine if the result of {@link #calculateBlockedUntilTerminalCount} + * has prioritized tranches of receivers. */ @VisibleForTesting - static boolean isPrioritized(@NonNull List<Object> receivers) { - int firstPriority = 0; - for (int i = 0; i < receivers.size(); i++) { - final int thisPriority = getReceiverPriority(receivers.get(i)); - if (i == 0) { - firstPriority = thisPriority; - } else if (thisPriority != firstPriority) { - return true; + static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount, + boolean ordered) { + return !ordered && (blockedUntilTerminalCount.length > 0) + && (blockedUntilTerminalCount[0] != -1); + } + + /** + * Calculate the {@link #terminalCount} that each receiver should be + * considered blocked until. + * <p> + * For example, in an ordered broadcast, receiver {@code N} is blocked until + * receiver {@code N-1} reaches a terminal state. Similarly, in a + * prioritized broadcast, receiver {@code N} is blocked until all receivers + * of a higher priority reach a terminal state. + * <p> + * When there are no terminal count constraints, the blocked value for each + * receiver is {@code -1}. + */ + @VisibleForTesting + static @NonNull int[] calculateBlockedUntilTerminalCount( + @NonNull List<Object> receivers, boolean ordered) { + final int N = receivers.size(); + final int[] blockedUntilTerminalCount = new int[N]; + int lastPriority = 0; + int lastPriorityIndex = 0; + for (int i = 0; i < N; i++) { + if (ordered) { + // When sending an ordered broadcast, we need to block this + // receiver until all previous receivers have terminated + blockedUntilTerminalCount[i] = i; + } else { + // When sending a prioritized broadcast, we only need to wait + // for the previous tranche of receivers to be terminated + final int thisPriority = getReceiverPriority(receivers.get(i)); + if ((i == 0) || (thisPriority != lastPriority)) { + lastPriority = thisPriority; + lastPriorityIndex = i; + blockedUntilTerminalCount[i] = i; + } else { + blockedUntilTerminalCount[i] = lastPriorityIndex; + } } } - return false; + // If the entire list is in the same priority tranche, mark as -1 to + // indicate that none of them need to wait + if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) { + Arrays.fill(blockedUntilTerminalCount, -1); + } + return blockedUntilTerminalCount; } - static int getReceiverUid(@NonNull Object receiver) { if (receiver instanceof BroadcastFilter) { return ((BroadcastFilter) receiver).owningUid; diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java index 60fddf0c7f22..481ab17b609e 100644 --- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java +++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java @@ -21,6 +21,7 @@ import static com.android.server.am.ActivityManagerService.checkComponentPermiss import static com.android.server.am.BroadcastQueue.TAG; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -42,6 +43,8 @@ import android.util.Slog; import com.android.internal.util.ArrayUtils; +import java.util.Objects; + /** * Policy logic that decides if delivery of a particular {@link BroadcastRecord} * should be skipped for a given {@link ResolveInfo} or {@link BroadcastFilter}. @@ -51,8 +54,8 @@ import com.android.internal.util.ArrayUtils; public class BroadcastSkipPolicy { private final ActivityManagerService mService; - public BroadcastSkipPolicy(ActivityManagerService service) { - mService = service; + public BroadcastSkipPolicy(@NonNull ActivityManagerService service) { + mService = Objects.requireNonNull(service); } /** @@ -60,18 +63,39 @@ public class BroadcastSkipPolicy { * the given {@link BroadcastFilter} or {@link ResolveInfo}. */ public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) { + final String msg = shouldSkipMessage(r, target); + if (msg != null) { + Slog.w(TAG, msg); + return true; + } else { + return false; + } + } + + /** + * Determine if the given {@link BroadcastRecord} is eligible to be sent to + * the given {@link BroadcastFilter} or {@link ResolveInfo}. + * + * @return message indicating why the argument should be skipped, otherwise + * {@code null} if it can proceed. + */ + public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) { if (target instanceof BroadcastFilter) { - return shouldSkip(r, (BroadcastFilter) target); + return shouldSkipMessage(r, (BroadcastFilter) target); } else { - return shouldSkip(r, (ResolveInfo) target); + return shouldSkipMessage(r, (ResolveInfo) target); } } /** * Determine if the given {@link BroadcastRecord} is eligible to be sent to * the given {@link ResolveInfo}. + * + * @return message indicating why the argument should be skipped, otherwise + * {@code null} if it can proceed. */ - public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) { + private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, + @NonNull ResolveInfo info) { final BroadcastOptions brOptions = r.options; final ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, @@ -82,58 +106,52 @@ public class BroadcastSkipPolicy { < brOptions.getMinManifestReceiverApiLevel() || info.activityInfo.applicationInfo.targetSdkVersion > brOptions.getMaxManifestReceiverApiLevel())) { - Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo + return "Target SDK mismatch: receiver " + info.activityInfo + " targets " + info.activityInfo.applicationInfo.targetSdkVersion + " but delivery restricted to [" + brOptions.getMinManifestReceiverApiLevel() + ", " + brOptions.getMaxManifestReceiverApiLevel() - + "] broadcasting " + broadcastDescription(r, component)); - return true; + + "] broadcasting " + broadcastDescription(r, component); } if (brOptions != null && !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) { - Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component) + return "Compat change filtered: broadcasting " + broadcastDescription(r, component) + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change " - + r.options.getRequireCompatChangeId()); - return true; + + r.options.getRequireCompatChangeId(); } if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid, component.getPackageName(), info.activityInfo.applicationInfo.uid)) { - Slog.w(TAG, "Association not allowed: broadcasting " - + broadcastDescription(r, component)); - return true; + return "Association not allowed: broadcasting " + + broadcastDescription(r, component); } if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid)) { - Slog.w(TAG, "Firewall blocked: broadcasting " - + broadcastDescription(r, component)); - return true; + return "Firewall blocked: broadcasting " + + broadcastDescription(r, component); } int perm = checkComponentPermission(info.activityInfo.permission, r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid, info.activityInfo.exported); if (perm != PackageManager.PERMISSION_GRANTED) { if (!info.activityInfo.exported) { - Slog.w(TAG, "Permission Denial: broadcasting " + return "Permission Denial: broadcasting " + broadcastDescription(r, component) - + " is not exported from uid " + info.activityInfo.applicationInfo.uid); + + " is not exported from uid " + info.activityInfo.applicationInfo.uid; } else { - Slog.w(TAG, "Permission Denial: broadcasting " + return "Permission Denial: broadcasting " + broadcastDescription(r, component) - + " requires " + info.activityInfo.permission); + + " requires " + info.activityInfo.permission; } - return true; } else if (info.activityInfo.permission != null) { final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission); if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid, r.callerPackage, r.callerFeatureId, "Broadcast delivered to " + info.activityInfo.name) != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Appop Denial: broadcasting " + return "Appop Denial: broadcasting " + broadcastDescription(r, component) + " requires appop " + AppOpsManager.permissionToOp( - info.activityInfo.permission)); - return true; + info.activityInfo.permission); } } @@ -142,38 +160,34 @@ public class BroadcastSkipPolicy { android.Manifest.permission.INTERACT_ACROSS_USERS, info.activityInfo.applicationInfo.uid) != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString() + return "Permission Denial: Receiver " + component.flattenToShortString() + " requests FLAG_SINGLE_USER, but app does not hold " - + android.Manifest.permission.INTERACT_ACROSS_USERS); - return true; + + android.Manifest.permission.INTERACT_ACROSS_USERS; } } if (info.activityInfo.applicationInfo.isInstantApp() && r.callingUid != info.activityInfo.applicationInfo.uid) { - Slog.w(TAG, "Instant App Denial: receiving " + return "Instant App Denial: receiving " + r.intent + " to " + component.flattenToShortString() + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")" - + " Instant Apps do not support manifest receivers"); - return true; + + " Instant Apps do not support manifest receivers"; } if (r.callerInstantApp && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0 && r.callingUid != info.activityInfo.applicationInfo.uid) { - Slog.w(TAG, "Instant App Denial: receiving " + return "Instant App Denial: receiving " + r.intent + " to " + component.flattenToShortString() + " requires receiver have visibleToInstantApps set" + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } if (r.curApp != null && r.curApp.mErrorState.isCrashing()) { // If the target process is crashing, just skip it. - Slog.w(TAG, "Skipping deliver ordered [" + r.queue.toString() + "] " + r - + " to " + r.curApp + ": process crashing"); - return true; + return "Skipping deliver ordered [" + r.queue.toString() + "] " + r + + " to " + r.curApp + ": process crashing"; } boolean isAvailable = false; @@ -183,15 +197,13 @@ public class BroadcastSkipPolicy { UserHandle.getUserId(info.activityInfo.applicationInfo.uid)); } catch (Exception e) { // all such failures mean we skip this receiver - Slog.w(TAG, "Exception getting recipient info for " - + info.activityInfo.packageName, e); + return "Exception getting recipient info for " + + info.activityInfo.packageName; } if (!isAvailable) { - Slog.w(TAG, - "Skipping delivery to " + info.activityInfo.packageName + " / " + return "Skipping delivery to " + info.activityInfo.packageName + " / " + info.activityInfo.applicationInfo.uid - + " : package no longer available"); - return true; + + " : package no longer available"; } // If permissions need a review before any of the app components can run, we drop @@ -201,10 +213,8 @@ public class BroadcastSkipPolicy { if (!requestStartTargetPermissionsReviewIfNeededLocked(r, info.activityInfo.packageName, UserHandle.getUserId( info.activityInfo.applicationInfo.uid))) { - Slog.w(TAG, - "Skipping delivery: permission review required for " - + broadcastDescription(r, component)); - return true; + return "Skipping delivery: permission review required for " + + broadcastDescription(r, component); } final int allowed = mService.getAppStartModeLOSP( @@ -216,10 +226,9 @@ public class BroadcastSkipPolicy { // to it and the app is in a state that should not receive it // (depending on how getAppStartModeLOSP has determined that). if (allowed == ActivityManager.APP_START_MODE_DISABLED) { - Slog.w(TAG, "Background execution disabled: receiving " + return "Background execution disabled: receiving " + r.intent + " to " - + component.flattenToShortString()); - return true; + + component.flattenToShortString(); } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0) || (r.intent.getComponent() == null && r.intent.getPackage() == null @@ -228,10 +237,9 @@ public class BroadcastSkipPolicy { && !isSignaturePerm(r.requiredPermissions))) { mService.addBackgroundCheckViolationLocked(r.intent.getAction(), component.getPackageName()); - Slog.w(TAG, "Background execution not allowed: receiving " + return "Background execution not allowed: receiving " + r.intent + " to " - + component.flattenToShortString()); - return true; + + component.flattenToShortString(); } } @@ -239,10 +247,8 @@ public class BroadcastSkipPolicy { && !mService.mUserController .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid), 0 /* flags */)) { - Slog.w(TAG, - "Skipping delivery to " + info.activityInfo.packageName + " / " - + info.activityInfo.applicationInfo.uid + " : user is not running"); - return true; + return "Skipping delivery to " + info.activityInfo.packageName + " / " + + info.activityInfo.applicationInfo.uid + " : user is not running"; } if (r.excludedPermissions != null && r.excludedPermissions.length > 0) { @@ -268,13 +274,15 @@ public class BroadcastSkipPolicy { info.activityInfo.applicationInfo.uid, info.activityInfo.packageName) == AppOpsManager.MODE_ALLOWED)) { - return true; + return "Skipping delivery to " + info.activityInfo.packageName + + " due to excluded permission " + excludedPermission; } } else { // When there is no app op associated with the permission, // skip when permission is granted. if (perm == PackageManager.PERMISSION_GRANTED) { - return true; + return "Skipping delivery to " + info.activityInfo.packageName + + " due to excluded permission " + excludedPermission; } } } @@ -283,13 +291,12 @@ public class BroadcastSkipPolicy { // Check that the receiver does *not* belong to any of the excluded packages if (r.excludedPackages != null && r.excludedPackages.length > 0) { if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) { - Slog.w(TAG, "Skipping delivery of excluded package " + return "Skipping delivery of excluded package " + r.intent + " to " + component.flattenToShortString() + " excludes package " + component.getPackageName() + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } } @@ -307,95 +314,94 @@ public class BroadcastSkipPolicy { perm = PackageManager.PERMISSION_DENIED; } if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " + return "Permission Denial: receiving " + r.intent + " to " + component.flattenToShortString() + " requires " + requiredPermission + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } int appOp = AppOpsManager.permissionToOpCode(requiredPermission); if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) { if (!noteOpForManifestReceiver(appOp, r, info, component)) { - return true; + return "Skipping delivery to " + info.activityInfo.packageName + + " due to required appop " + appOp; } } } } if (r.appOp != AppOpsManager.OP_NONE) { if (!noteOpForManifestReceiver(r.appOp, r, info, component)) { - return true; + return "Skipping delivery to " + info.activityInfo.packageName + + " due to required appop " + r.appOp; } } - return false; + return null; } /** * Determine if the given {@link BroadcastRecord} is eligible to be sent to * the given {@link BroadcastFilter}. + * + * @return message indicating why the argument should be skipped, otherwise + * {@code null} if it can proceed. */ - public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull BroadcastFilter filter) { + private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, + @NonNull BroadcastFilter filter) { if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) { - Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString() + return "Compat change filtered: broadcasting " + r.intent.toString() + " to uid " + filter.owningUid + " due to compat change " - + r.options.getRequireCompatChangeId()); - return true; + + r.options.getRequireCompatChangeId(); } if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid, filter.packageName, filter.owningUid)) { - Slog.w(TAG, "Association not allowed: broadcasting " + return "Association not allowed: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ") to " + filter.packageName + " through " - + filter); - return true; + + filter; } if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, r.callingPid, r.resolvedType, filter.receiverList.uid)) { - Slog.w(TAG, "Firewall blocked: broadcasting " + return "Firewall blocked: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ") to " + filter.packageName + " through " - + filter); - return true; + + filter; } // Check that the sender has permission to send to this receiver if (filter.requiredPermission != null) { int perm = checkComponentPermission(filter.requiredPermission, r.callingPid, r.callingUid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: broadcasting " + return "Permission Denial: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + " requires " + filter.requiredPermission - + " due to registered receiver " + filter); - return true; + + " due to registered receiver " + filter; } else { final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission); if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid, r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver") != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Appop Denial: broadcasting " + return "Appop Denial: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + " requires appop " + AppOpsManager.permissionToOp( filter.requiredPermission) - + " due to registered receiver " + filter); - return true; + + " due to registered receiver " + filter; } } } if ((filter.receiverList.app == null || filter.receiverList.app.isKilled() || filter.receiverList.app.mErrorState.isCrashing())) { - Slog.w(TAG, "Skipping deliver [" + r.queue.toString() + "] " + r - + " to " + filter.receiverList + ": process gone or crashing"); - return true; + return "Skipping deliver [" + r.queue.toString() + "] " + r + + " to " + filter.receiverList + ": process gone or crashing"; } // Ensure that broadcasts are only sent to other Instant Apps if they are marked as @@ -405,28 +411,26 @@ public class BroadcastSkipPolicy { if (!visibleToInstantApps && filter.instantApp && filter.receiverList.uid != r.callingUid) { - Slog.w(TAG, "Instant App Denial: receiving " + return "Instant App Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")" - + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS"); - return true; + + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS"; } if (!filter.visibleToInstantApp && r.callerInstantApp && filter.receiverList.uid != r.callingUid) { - Slog.w(TAG, "Instant App Denial: receiving " + return "Instant App Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " requires receiver be visible to instant apps" + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } // Check that the receiver has the required permission(s) to receive this broadcast. @@ -436,15 +440,14 @@ public class BroadcastSkipPolicy { int perm = checkComponentPermission(requiredPermission, filter.receiverList.pid, filter.receiverList.uid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " + return "Permission Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " requires " + requiredPermission + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } int appOp = AppOpsManager.permissionToOpCode(requiredPermission); if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp @@ -452,7 +455,7 @@ public class BroadcastSkipPolicy { filter.receiverList.uid, filter.packageName, filter.featureId, "Broadcast delivered to registered receiver " + filter.receiverId) != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Appop Denial: receiving " + return "Appop Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid @@ -460,8 +463,7 @@ public class BroadcastSkipPolicy { + " requires appop " + AppOpsManager.permissionToOp( requiredPermission) + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } } } @@ -469,14 +471,13 @@ public class BroadcastSkipPolicy { int perm = checkComponentPermission(null, filter.receiverList.pid, filter.receiverList.uid, -1, true); if (perm != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: security check failed when receiving " + return "Permission Denial: security check failed when receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } } // Check that the receiver does *not* have any excluded permissions @@ -496,7 +497,7 @@ public class BroadcastSkipPolicy { filter.receiverList.uid, filter.packageName) == AppOpsManager.MODE_ALLOWED)) { - Slog.w(TAG, "Appop Denial: receiving " + return "Appop Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid @@ -504,22 +505,20 @@ public class BroadcastSkipPolicy { + " excludes appop " + AppOpsManager.permissionToOp( excludedPermission) + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } } else { // When there is no app op associated with the permission, // skip when permission is granted. if (perm == PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Permission Denial: receiving " + return "Permission Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " excludes " + excludedPermission + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } } } @@ -528,15 +527,14 @@ public class BroadcastSkipPolicy { // Check that the receiver does *not* belong to any of the excluded packages if (r.excludedPackages != null && r.excludedPackages.length > 0) { if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) { - Slog.w(TAG, "Skipping delivery of excluded package " + return "Skipping delivery of excluded package " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " excludes package " + filter.packageName + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } } @@ -546,15 +544,14 @@ public class BroadcastSkipPolicy { filter.receiverList.uid, filter.packageName, filter.featureId, "Broadcast delivered to registered receiver " + filter.receiverId) != AppOpsManager.MODE_ALLOWED) { - Slog.w(TAG, "Appop Denial: receiving " + return "Appop Denial: receiving " + r.intent.toString() + " to " + filter.receiverList.app + " (pid=" + filter.receiverList.pid + ", uid=" + filter.receiverList.uid + ")" + " requires appop " + AppOpsManager.opToName(r.appOp) + " due to sender " + r.callerPackage - + " (uid " + r.callingUid + ")"); - return true; + + " (uid " + r.callingUid + ")"; } // Ensure that broadcasts are only sent to other apps if they are explicitly marked as @@ -562,15 +559,14 @@ public class BroadcastSkipPolicy { if (!filter.exported && checkComponentPermission(null, r.callingPid, r.callingUid, filter.receiverList.uid, filter.exported) != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "Exported Denial: sending " + return "Exported Denial: sending " + r.intent.toString() + ", action: " + r.intent.getAction() + " from " + r.callerPackage + " (uid=" + r.callingUid + ")" + " due to receiver " + filter.receiverList.app + " (uid " + filter.receiverList.uid + ")" - + " not specifying RECEIVER_EXPORTED"); - return true; + + " not specifying RECEIVER_EXPORTED"; } // If permissions need a review before any of the app components can run, we drop @@ -579,10 +575,10 @@ public class BroadcastSkipPolicy { // broadcast. if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName, filter.owningUserId)) { - return true; + return "Skipping delivery to " + filter.packageName + " due to permissions review"; } - return false; + return null; } private static String broadcastDescription(BroadcastRecord r, ComponentName component) { diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 740efbc658ba..3f5f3bce3014 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -18,7 +18,6 @@ package com.android.server.am; import static android.app.ActivityManager.START_SUCCESS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -36,7 +35,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PowerWhitelistManager; import android.os.PowerWhitelistManager.ReasonCode; -import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.TransactionTooLargeException; @@ -441,17 +439,8 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // Only system senders can declare a broadcast to be alarm-originated. We check // this here rather than in the general case handling below to fail before the other // invocation side effects such as allowlisting. - if (options != null && callingUid != Process.SYSTEM_UID - && key.type == ActivityManager.INTENT_SENDER_BROADCAST) { - if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST) - || options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) { - if (DEBUG_BROADCAST_LIGHT) { - Slog.w(TAG, "Non-system caller " + callingUid - + " may not flag broadcast as alarm or interactive"); - } - throw new SecurityException( - "Non-system callers may not flag broadcasts as alarm or interactive"); - } + if (key.type == ActivityManager.INTENT_SENDER_BROADCAST) { + controller.mAmInternal.enforceBroadcastOptionsPermissions(options, callingUid); } final long origId = Binder.clearCallingIdentity(); @@ -490,6 +479,11 @@ public final class PendingIntentRecord extends IIntentSender.Stub { final IApplicationThread finishedReceiverThread = caller; boolean sendFinish = finishedReceiver != null; + if ((finishedReceiver != null) && (finishedReceiverThread == null)) { + Slog.w(TAG, "Sending of " + intent + " from " + Binder.getCallingUid() + + " requested resultTo without an IApplicationThread!", new Throwable()); + } + int userId = key.userId; if (userId == UserHandle.USER_CURRENT) { userId = controller.mUserController.getCurrentOrTargetUserId(); diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 71d39964ed72..68d906be65de 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -44,7 +44,7 @@ import android.os.incremental.IncrementalMetrics; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; -import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.CompositeRWLock; import com.android.internal.annotations.GuardedBy; @@ -62,6 +62,12 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicLong; + + /** * The error state of the process, such as if it's crashing/ANR etc. */ @@ -257,12 +263,13 @@ class ProcessErrorStateRecord { void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo, String parentShortComponentName, WindowProcessController parentProcess, boolean aboveSystem, TimeoutRecord timeoutRecord, - boolean onlyDumpSelf) { + ExecutorService auxiliaryTaskExecutor, boolean onlyDumpSelf) { String annotation = timeoutRecord.mReason; AnrLatencyTracker latencyTracker = timeoutRecord.mLatencyTracker; + Future<?> updateCpuStatsNowFirstCall = null; ArrayList<Integer> firstPids = new ArrayList<>(5); - SparseArray<Boolean> lastPids = new SparseArray<>(20); + SparseBooleanArray lastPids = new SparseBooleanArray(20); mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> { latencyTracker.waitingOnAMSLockStarted(); @@ -275,10 +282,15 @@ class ProcessErrorStateRecord { }); long anrTime = SystemClock.uptimeMillis(); + if (isMonitorCpuUsage()) { - latencyTracker.updateCpuStatsNowCalled(); - mService.updateCpuStatsNow(); - latencyTracker.updateCpuStatsNowReturned(); + updateCpuStatsNowFirstCall = auxiliaryTaskExecutor.submit( + () -> { + latencyTracker.updateCpuStatsNowCalled(); + mService.updateCpuStatsNow(); + latencyTracker.updateCpuStatsNowReturned(); + }); + } final boolean isSilentAnr; @@ -367,7 +379,7 @@ class ProcessErrorStateRecord { firstPids.add(myPid); if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r); } else { - lastPids.put(myPid, Boolean.TRUE); + lastPids.put(myPid, true); if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r); } } @@ -430,41 +442,59 @@ class ProcessErrorStateRecord { report.append(currentPsiState); ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); - latencyTracker.nativePidCollectionStarted(); - // don't dump native PIDs for background ANRs unless it is the process of interest - String[] nativeProcs = null; - if (isSilentAnr || onlyDumpSelf) { - for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) { - if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) { - nativeProcs = new String[] { mApp.processName }; - break; - } - } - } else { - nativeProcs = NATIVE_STACKS_OF_INTEREST; - } + // We push the native pids collection task to the helper thread through + // the Anr auxiliary task executor, and wait on it later after dumping the first pids + Future<ArrayList<Integer>> nativePidsFuture = + auxiliaryTaskExecutor.submit( + () -> { + latencyTracker.nativePidCollectionStarted(); + // don't dump native PIDs for background ANRs unless + // it is the process of interest + String[] nativeProcs = null; + if (isSilentAnr || onlyDumpSelf) { + for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) { + if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) { + nativeProcs = new String[] { mApp.processName }; + break; + } + } + } else { + nativeProcs = NATIVE_STACKS_OF_INTEREST; + } - int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs); - ArrayList<Integer> nativePids = null; + int[] pids = nativeProcs == null + ? null : Process.getPidsForCommands(nativeProcs); + ArrayList<Integer> nativePids = null; + + if (pids != null) { + nativePids = new ArrayList<>(pids.length); + for (int i : pids) { + nativePids.add(i); + } + } + latencyTracker.nativePidCollectionEnded(); + return nativePids; + }); - if (pids != null) { - nativePids = new ArrayList<>(pids.length); - for (int i : pids) { - nativePids.add(i); - } - } - latencyTracker.nativePidCollectionEnded(); // For background ANRs, don't pass the ProcessCpuTracker to // avoid spending 1/2 second collecting stats to rank lastPids. StringWriter tracesFileException = new StringWriter(); // To hold the start and end offset to the ANR trace file respectively. - final long[] offsets = new long[2]; + final AtomicLong firstPidEndOffset = new AtomicLong(-1); File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids, - nativePids, tracesFileException, offsets, annotation, criticalEventLog, - latencyTracker); + nativePidsFuture, tracesFileException, firstPidEndOffset, annotation, + criticalEventLog, auxiliaryTaskExecutor, latencyTracker); if (isMonitorCpuUsage()) { + // Wait for the first call to finish + try { + updateCpuStatsNowFirstCall.get(); + } catch (ExecutionException e) { + Slog.w(TAG, "Failed to update the CPU stats", e.getCause()); + } catch (InterruptedException e) { + Slog.w(TAG, "Interrupted while updating the CPU stats", e); + } mService.updateCpuStatsNow(); mService.mAppProfiler.printCurrentCpuState(report, anrTime); info.append(processCpuTracker.printCurrentLoad()); @@ -478,10 +508,14 @@ class ProcessErrorStateRecord { if (tracesFile == null) { // There is no trace file, so dump (only) the alleged culprit's threads to the log Process.sendSignal(pid, Process.SIGNAL_QUIT); - } else if (offsets[1] > 0) { + } else if (firstPidEndOffset.get() > 0) { // We've dumped into the trace file successfully + // We pass the start and end offsets of the first section of + // the ANR file (the headers and first process dump) + final long startOffset = 0L; + final long endOffset = firstPidEndOffset.get(); mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace( - pid, mApp.uid, mApp.getPackageList(), tracesFile, offsets[0], offsets[1]); + pid, mApp.uid, mApp.getPackageList(), tracesFile, startOffset, endOffset); } // Check if package is still being loaded diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index dcc7a8ea4e44..8d3890ce9d5a 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -120,6 +120,7 @@ import com.android.server.SystemService.UserCompletedEventType; import com.android.server.SystemServiceManager; import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserManagerInternal; +import com.android.server.pm.UserManagerInternal.UserLifecycleListener; import com.android.server.pm.UserManagerService; import com.android.server.utils.Slogf; import com.android.server.utils.TimingsTraceAndSlog; @@ -320,8 +321,12 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private int[] mStartedUserArray = new int[] { 0 }; - // If there are multiple profiles for the current user, their ids are here - // Currently only the primary user can have managed profiles + /** + * Contains the current user and its profiles (if any). + * + * <p><b>NOTE: </b>it lists all profiles, regardless of their running state (i.e., they're in + * this list even if not running). + */ @GuardedBy("mLock") private int[] mCurrentProfileIds = new int[] {}; @@ -436,6 +441,18 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray(); + private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() { + @Override + public void onUserCreated(UserInfo user, Object token) { + onUserAdded(user); + } + + @Override + public void onUserRemoved(UserInfo user) { + UserController.this.onUserRemoved(user.id); + } + }; + UserController(ActivityManagerService service) { this(new Injector(service)); } @@ -1667,7 +1684,7 @@ class UserController implements Handler.Callback { userSwitchUiEnabled = mUserSwitchUiEnabled; } mInjector.updateUserConfiguration(); - updateCurrentProfileIds(); + updateProfileRelatedCaches(); mInjector.getWindowManager().setCurrentUser(userId); mInjector.reportCurWakefulnessUsageEvent(); // Once the internal notion of the active user has switched, we lock the device @@ -1681,7 +1698,7 @@ class UserController implements Handler.Callback { } } else { final Integer currentUserIdInt = mCurrentUserId; - updateCurrentProfileIds(); + updateProfileRelatedCaches(); synchronized (mLock) { mUserLru.remove(currentUserIdInt); mUserLru.add(currentUserIdInt); @@ -2168,8 +2185,6 @@ class UserController implements Handler.Callback { mHandler.sendMessage(mHandler.obtainMessage(COMPLETE_USER_SWITCH_MSG, newUserId, 0)); uss.switching = false; - mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); - mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); stopGuestOrEphemeralUserIfBackground(oldUserId); stopUserOnSwitchIfEnforced(oldUserId); if (oldUserId == UserHandle.USER_SYSTEM) { @@ -2183,21 +2198,22 @@ class UserController implements Handler.Callback { @VisibleForTesting void completeUserSwitch(int newUserId) { - if (isUserSwitchUiEnabled()) { - // If there is no challenge set, dismiss the keyguard right away - if (!mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { - // Wait until the keyguard is dismissed to unfreeze - mInjector.dismissKeyguard( - new Runnable() { - public void run() { - unfreezeScreen(); - } - }, - "User Switch"); - return; - } else { + final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled(); + final Runnable runnable = () -> { + if (isUserSwitchUiEnabled) { unfreezeScreen(); } + mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG); + mHandler.sendMessage(mHandler.obtainMessage( + REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0)); + }; + + // If there is no challenge set, dismiss the keyguard right away + if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) { + // Wait until the keyguard is dismissed to unfreeze + mInjector.dismissKeyguard(runnable, "User Switch"); + } else { + runnable.run(); } } @@ -2526,7 +2542,8 @@ class UserController implements Handler.Callback { Slogf.d(TAG, "onSystemReady()"); } - updateCurrentProfileIds(); + mInjector.getUserManagerInternal().addUserLifecycleListener(mUserLifecycleListener); + updateProfileRelatedCaches(); mInjector.reportCurWakefulnessUsageEvent(); } @@ -2551,13 +2568,13 @@ class UserController implements Handler.Callback { } /** - * Refreshes the list of users related to the current user when either a - * user switch happens or when a new related user is started in the - * background. + * Refreshes the internal caches related to user profiles. + * + * <p>It's called every time a user is started. */ - private void updateCurrentProfileIds() { + private void updateProfileRelatedCaches() { final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(), - false /* enabledOnly */); + /* enabledOnly= */ false); int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null for (int i = 0; i < currentProfileIds.length; i++) { currentProfileIds[i] = profiles.get(i).id; @@ -2824,6 +2841,18 @@ class UserController implements Handler.Callback { } } + private void onUserAdded(UserInfo user) { + if (!user.isProfile()) { + return; + } + synchronized (mLock) { + if (user.profileGroupId == mCurrentUserId) { + mCurrentProfileIds = ArrayUtils.appendInt(mCurrentProfileIds, user.id); + } + mUserProfileGroupIds.put(user.id, user.profileGroupId); + } + } + void onUserRemoved(@UserIdInt int userId) { synchronized (mLock) { int size = mUserProfileGroupIds.size(); @@ -2938,6 +2967,10 @@ class UserController implements Handler.Callback { for (int i = 0; i < mVisibleUsers.size(); i++) { proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i)); } + proto.write(UserControllerProto.CURRENT_USER, mCurrentUserId); + for (int i = 0; i < mCurrentProfileIds.length; i++) { + proto.write(UserControllerProto.CURRENT_PROFILES, mCurrentProfileIds[i]); + } proto.end(token); } } @@ -2975,6 +3008,7 @@ class UserController implements Handler.Callback { pw.println(mUserProfileGroupIds.valueAt(i)); } } + pw.println(" mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds)); pw.println(" mCurrentUserId:" + mCurrentUserId); pw.println(" mTargetUserId:" + mTargetUserId); pw.println(" mLastActiveUsers:" + mLastActiveUsers); diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 4aaf1abf4147..908cb3f0c720 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -185,6 +185,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @Override @EnforcePermission(MANAGE_GAME_ACTIVITY) public void createGameSession(int taskId) { + super.createGameSession_enforcePermission(); + mBackgroundExecutor.execute(() -> { GameServiceProviderInstanceImpl.this.createGameSession(taskId); }); @@ -197,6 +199,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @EnforcePermission(MANAGE_GAME_ACTIVITY) public void takeScreenshot(int taskId, @NonNull AndroidFuture gameScreenshotResultFuture) { + super.takeScreenshot_enforcePermission(); + mBackgroundExecutor.execute(() -> { GameServiceProviderInstanceImpl.this.takeScreenshot(taskId, gameScreenshotResultFuture); @@ -206,6 +210,8 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @Override @EnforcePermission(MANAGE_GAME_ACTIVITY) public void restartGame(int taskId) { + super.restartGame_enforcePermission(); + mBackgroundExecutor.execute(() -> { GameServiceProviderInstanceImpl.this.restartGame(taskId); }); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index bbffc894ef3e..2fe06094bd88 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -286,22 +286,9 @@ import java.util.concurrent.atomic.AtomicBoolean; if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid); } - - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - AudioDeviceAttributes device = null; - if (on) { - device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""); - } else { - CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); - if (client == null || !client.requestsSpeakerphone()) { - return; - } - } - postSetCommunicationRouteForClient(new CommunicationClientInfo( - cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource)); - } - } + postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( + cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), + on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); } /** @@ -311,6 +298,9 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param device Device selected or null to unselect. * @param eventSource for logging purposes */ + + private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000; + /*package*/ boolean setCommunicationDevice( IBinder cb, int pid, AudioDeviceInfo device, String eventSource) { @@ -318,21 +308,53 @@ import java.util.concurrent.atomic.AtomicBoolean; Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid); } - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - AudioDeviceAttributes deviceAttr = null; - if (device != null) { - deviceAttr = new AudioDeviceAttributes(device); - } else { - CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); - if (client == null) { - return false; + AudioDeviceAttributes deviceAttr = + (device != null) ? new AudioDeviceAttributes(device) : null; + CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr, + device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true); + postSetCommunicationDeviceForClient(deviceInfo); + boolean status; + synchronized (deviceInfo) { + final long start = System.currentTimeMillis(); + long elapsed = 0; + while (deviceInfo.mWaitForStatus) { + try { + deviceInfo.wait(SET_COMMUNICATION_DEVICE_TIMEOUT_MS - elapsed); + } catch (InterruptedException e) { + elapsed = System.currentTimeMillis() - start; + if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) { + deviceInfo.mStatus = false; + deviceInfo.mWaitForStatus = false; } } - postSetCommunicationRouteForClient(new CommunicationClientInfo( - cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource)); } + status = deviceInfo.mStatus; } + return status; + } + + /** + * Sets or resets the communication device for matching client. If no client matches and the + * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop. + * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo} + * @return true if the communication device is set or reset + */ + @GuardedBy("mDeviceStateLock") + /*package*/ boolean onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo); + } + if (!deviceInfo.mOn) { + CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid); + if (client == null || (deviceInfo.mDevice != null + && !deviceInfo.mDevice.equals(client.getDevice()))) { + return false; + } + } + + AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null; + setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device, + deviceInfo.mScoAudioMode, deviceInfo.mEventSource); return true; } @@ -390,7 +412,7 @@ import java.util.concurrent.atomic.AtomicBoolean; mBtHelper.stopBluetoothSco(eventSource); } - sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource); + updateCommunicationRoute(eventSource); } /** @@ -424,7 +446,7 @@ import java.util.concurrent.atomic.AtomicBoolean; CommunicationRouteClient crc = topCommunicationRouteClient(); AudioDeviceAttributes device = crc != null ? crc.getDevice() : null; if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "requestedCommunicationDevice, device: " + Log.v(TAG, "requestedCommunicationDevice: " + device + " mAudioModeOwner: " + mAudioModeOwner.toString()); } return device; @@ -822,37 +844,22 @@ import java.util.concurrent.atomic.AtomicBoolean; @NonNull String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid); - } - - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - AudioDeviceAttributes device = - new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""); - - postSetCommunicationRouteForClient(new CommunicationClientInfo( - cb, pid, device, scoAudioMode, eventSource)); - } + Log.v(TAG, "startBluetoothScoForClient, pid: " + pid); } + postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( + cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), + true, scoAudioMode, eventSource, false)); } /*package*/ void stopBluetoothScoForClient( IBinder cb, int pid, @NonNull String eventSource) { if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid); - } - - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - CommunicationRouteClient client = getCommunicationRouteClientForPid(pid); - if (client == null || !client.requestsBluetoothSco()) { - return; - } - postSetCommunicationRouteForClient(new CommunicationClientInfo( - cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource)); - } + Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid); } + postSetCommunicationDeviceForClient(new CommunicationDeviceInfo( + cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""), + false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false)); } /*package*/ int setPreferredDevicesForStrategySync(int strategy, @@ -990,7 +997,8 @@ import java.util.concurrent.atomic.AtomicBoolean; } //--------------------------------------------------------------------- - // Message handling on behalf of helper classes + // Message handling on behalf of helper classes. + // Each of these methods posts a message to mBrokerHandler message queue. /*package*/ void postBroadcastScoConnectionState(int state) { sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state); } @@ -1046,28 +1054,34 @@ import java.util.concurrent.atomic.AtomicBoolean; sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource); } - /*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) { - sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info); + /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) { + sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info); } /*package*/ void postScoAudioStateChanged(int state) { sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state); } - /*package*/ static final class CommunicationClientInfo { - final @NonNull IBinder mCb; - final int mPid; - final @NonNull AudioDeviceAttributes mDevice; - final int mScoAudioMode; - final @NonNull String mEventSource; + /*package*/ static final class CommunicationDeviceInfo { + final @NonNull IBinder mCb; // Identifies the requesting client for death handler + final int mPid; // Requester process ID + final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset. + final boolean mOn; // true if setting, false if resetting + final int mScoAudioMode; // only used for SCO: requested audio mode + final @NonNull String mEventSource; // caller identifier for logging + boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent) + boolean mStatus = false; // completion status only used if mWaitForStatus is true - CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device, - int scoAudioMode, @NonNull String eventSource) { + CommunicationDeviceInfo(@NonNull IBinder cb, int pid, + @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode, + @NonNull String eventSource, boolean waitForStatus) { mCb = cb; mPid = pid; mDevice = device; + mOn = on; mScoAudioMode = scoAudioMode; mEventSource = eventSource; + mWaitForStatus = waitForStatus; } // redefine equality op so we can match messages intended for this client @@ -1079,21 +1093,24 @@ import java.util.concurrent.atomic.AtomicBoolean; if (this == o) { return true; } - if (!(o instanceof CommunicationClientInfo)) { + if (!(o instanceof CommunicationDeviceInfo)) { return false; } - return mCb.equals(((CommunicationClientInfo) o).mCb) - && mPid == ((CommunicationClientInfo) o).mPid; + return mCb.equals(((CommunicationDeviceInfo) o).mCb) + && mPid == ((CommunicationDeviceInfo) o).mPid; } @Override public String toString() { - return "CommunicationClientInfo mCb=" + mCb.toString() - +"mPid=" + mPid - +"mDevice=" + mDevice.toString() - +"mScoAudioMode=" + mScoAudioMode - +"mEventSource=" + mEventSource; + return "CommunicationDeviceInfo mCb=" + mCb.toString() + + " mPid=" + mPid + + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]" + + " mOn=" + mOn + + " mScoAudioMode=" + mScoAudioMode + + " mEventSource=" + mEventSource + + " mWaitForStatus=" + mWaitForStatus + + " mStatus=" + mStatus; } } @@ -1297,7 +1314,7 @@ import java.util.concurrent.atomic.AtomicBoolean; updateActiveCommunicationDevice(); mDeviceInventory.onRestoreDevices(); mBtHelper.onAudioServerDiedRestoreA2dp(); - onUpdateCommunicationRoute("MSG_RESTORE_DEVICES"); + updateCommunicationRoute("MSG_RESTORE_DEVICES"); } } break; @@ -1392,28 +1409,27 @@ import java.util.concurrent.atomic.AtomicBoolean; } break; - case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT: + case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT: + CommunicationDeviceInfo deviceInfo = (CommunicationDeviceInfo) msg.obj; + boolean status; synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - CommunicationClientInfo info = (CommunicationClientInfo) msg.obj; - setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice, - info.mScoAudioMode, info.mEventSource); + status = onSetCommunicationDeviceForClient(deviceInfo); } } - break; - - case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT: - synchronized (mSetModeLock) { - synchronized (mDeviceStateLock) { - onUpdateCommunicationRouteClient((String) msg.obj); + synchronized (deviceInfo) { + if (deviceInfo.mWaitForStatus) { + deviceInfo.mStatus = status; + deviceInfo.mWaitForStatus = false; + deviceInfo.notify(); } } break; - case MSG_L_UPDATE_COMMUNICATION_ROUTE: + case MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT: synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - onUpdateCommunicationRoute((String) msg.obj); + onUpdateCommunicationRouteClient((String) msg.obj); } } break; @@ -1568,8 +1584,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37; private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38; - private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39; - private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42; + private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42; private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43; private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44; @@ -1793,18 +1808,6 @@ import java.util.concurrent.atomic.AtomicBoolean; AudioDeviceAttributes getDevice() { return mDevice; } - - boolean requestsBluetoothSco() { - return mDevice != null - && mDevice.getType() - == AudioDeviceInfo.TYPE_BLUETOOTH_SCO; - } - - boolean requestsSpeakerphone() { - return mDevice != null - && mDevice.getType() - == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; - } } // @GuardedBy("mSetModeLock") @@ -1852,14 +1855,14 @@ import java.util.concurrent.atomic.AtomicBoolean; */ // @GuardedBy("mSetModeLock") @GuardedBy("mDeviceStateLock") - private void onUpdateCommunicationRoute(String eventSource) { + private void updateCommunicationRoute(String eventSource) { AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice(); if (AudioService.DEBUG_COMM_RTE) { - Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: " + Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: " + preferredCommunicationDevice + " eventSource: " + eventSource); } AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "onUpdateCommunicationRoute, preferredCommunicationDevice: " + "updateCommunicationRoute, preferredCommunicationDevice: " + preferredCommunicationDevice + " eventSource: " + eventSource))); if (preferredCommunicationDevice == null @@ -1895,7 +1898,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // @GuardedBy("mSetModeLock") @GuardedBy("mDeviceStateLock") private void onUpdateCommunicationRouteClient(String eventSource) { - onUpdateCommunicationRoute(eventSource); + updateCommunicationRoute(eventSource); CommunicationRouteClient crc = topCommunicationRouteClient(); if (AudioService.DEBUG_COMM_RTE) { Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 9d6fa9ec06bf..f8a64623f52a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1859,6 +1859,8 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#setSupportedSystemUsages(int[]) */ public void setSupportedSystemUsages(@NonNull @AttributeSystemUsage int[] systemUsages) { + super.setSupportedSystemUsages_enforcePermission(); + verifySystemUsages(systemUsages); synchronized (mSupportedSystemUsagesLock) { @@ -1872,6 +1874,8 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#getSupportedSystemUsages() */ public @NonNull @AttributeSystemUsage int[] getSupportedSystemUsages() { + super.getSupportedSystemUsages_enforcePermission(); + synchronized (mSupportedSystemUsagesLock) { return Arrays.copyOf(mSupportedSystemUsages, mSupportedSystemUsages.length); } @@ -1893,6 +1897,8 @@ public class AudioService extends IAudioService.Stub @NonNull public List<AudioProductStrategy> getAudioProductStrategies() { // verify permissions + super.getAudioProductStrategies_enforcePermission(); + return AudioProductStrategy.getAudioProductStrategies(); } @@ -1904,6 +1910,8 @@ public class AudioService extends IAudioService.Stub @NonNull public List<AudioVolumeGroup> getAudioVolumeGroups() { // verify permissions + super.getAudioVolumeGroups_enforcePermission(); + return AudioVolumeGroup.getAudioVolumeGroups(); } @@ -2782,6 +2790,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */ public int removePreferredDevicesForStrategy(int strategy) { + super.removePreferredDevicesForStrategy_enforcePermission(); + final String logString = String.format("removePreferredDeviceForStrategy strat:%d", strategy); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -2799,6 +2809,8 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy) */ public List<AudioDeviceAttributes> getPreferredDevicesForStrategy(int strategy) { + super.getPreferredDevicesForStrategy_enforcePermission(); + List<AudioDeviceAttributes> devices = new ArrayList<>(); final long identity = Binder.clearCallingIdentity(); final int status = AudioSystem.getDevicesForRoleAndStrategy( @@ -2869,6 +2881,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */ public int clearPreferredDevicesForCapturePreset(int capturePreset) { + super.clearPreferredDevicesForCapturePreset_enforcePermission(); + final String logString = String.format( "removePreferredDeviceForCapturePreset source:%d", capturePreset); sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG)); @@ -2885,6 +2899,8 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#getPreferredDevicesForCapturePreset(int) */ public List<AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int capturePreset) { + super.getPreferredDevicesForCapturePreset_enforcePermission(); + List<AudioDeviceAttributes> devices = new ArrayList<>(); final long identity = Binder.clearCallingIdentity(); final int status = AudioSystem.getDevicesForRoleAndCapturePreset( @@ -3618,6 +3634,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */ public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags, String callingPackage, String attributionTag) { + super.setVolumeIndexForAttributes_enforcePermission(); + Objects.requireNonNull(attr, "attr must not be null"); final int volumeGroup = getVolumeGroupIdForAttributes(attr); if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { @@ -3661,6 +3679,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioManager#getVolumeIndexForAttributes(attr) */ public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + super.getVolumeIndexForAttributes_enforcePermission(); + Objects.requireNonNull(attr, "attr must not be null"); final int volumeGroup = getVolumeGroupIdForAttributes(attr); if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { @@ -3673,6 +3693,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */ public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + super.getMaxVolumeIndexForAttributes_enforcePermission(); + Objects.requireNonNull(attr, "attr must not be null"); return AudioSystem.getMaxVolumeIndexForAttributes(attr); } @@ -3680,6 +3702,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */ public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + super.getMinVolumeIndexForAttributes_enforcePermission(); + Objects.requireNonNull(attr, "attr must not be null"); return AudioSystem.getMinVolumeIndexForAttributes(attr); } @@ -3786,6 +3810,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND) /** @see AudioManager#isUltrasoundSupported() */ public boolean isUltrasoundSupported() { + super.isUltrasoundSupported_enforcePermission(); + return AudioSystem.isUltrasoundSupported(); } @@ -4603,6 +4629,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setMasterMute(boolean, int) */ public void setMasterMute(boolean mute, int flags, String callingPackage, int userId, String attributionTag) { + super.setMasterMute_enforcePermission(); + setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid(), userId, Binder.getCallingPid(), attributionTag); } @@ -4647,6 +4675,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE) /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { + super.getLastAudibleStreamVolume_enforcePermission(); + ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); return (mStreamStates[streamType].getIndex(device) + 5) / 10; @@ -5500,6 +5530,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#isPstnCallAudioInterceptable() */ public boolean isPstnCallAudioInterceptable() { + super.isPstnCallAudioInterceptable_enforcePermission(); + boolean uplinkDeviceFound = false; boolean downlinkDeviceFound = false; AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_ALL); @@ -6891,6 +6923,8 @@ public class AudioService extends IAudioService.Stub @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) { // verify permissions // verify arguments + super.setDeviceVolumeBehavior_enforcePermission(); + Objects.requireNonNull(device); AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" @@ -7046,6 +7080,8 @@ public class AudioService extends IAudioService.Stub */ public void setWiredDeviceConnectionState(AudioDeviceAttributes attributes, @ConnectionState int state, String caller) { + super.setWiredDeviceConnectionState_enforcePermission(); + if (state != CONNECTION_STATE_CONNECTED && state != CONNECTION_STATE_DISCONNECTED) { throw new IllegalArgumentException("Invalid state " + state); @@ -9178,24 +9214,32 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */ public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device) { + super.isSpatializerAvailableForDevice_enforcePermission(); + return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device)); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */ public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) { + super.hasHeadTracker_enforcePermission(); + return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device)); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */ public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) { + super.setHeadTrackerEnabled_enforcePermission(); + mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device)); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */ public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) { + super.isHeadTrackerEnabled_enforcePermission(); + return mSpatializerHelper.isHeadTrackerEnabled(Objects.requireNonNull(device)); } @@ -9207,6 +9251,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { + super.setSpatializerEnabled_enforcePermission(); + mSpatializerHelper.setFeatureEnabled(enabled); } @@ -9236,6 +9282,8 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ public void registerSpatializerHeadTrackingCallback( @NonNull ISpatializerHeadTrackingModeCallback cb) { + super.registerSpatializerHeadTrackingCallback_enforcePermission(); + Objects.requireNonNull(cb); mSpatializerHelper.registerHeadTrackingModeCallback(cb); } @@ -9244,6 +9292,8 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ public void unregisterSpatializerHeadTrackingCallback( @NonNull ISpatializerHeadTrackingModeCallback cb) { + super.unregisterSpatializerHeadTrackingCallback_enforcePermission(); + Objects.requireNonNull(cb); mSpatializerHelper.unregisterHeadTrackingModeCallback(cb); } @@ -9259,6 +9309,8 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */ public void registerHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { + super.registerHeadToSoundstagePoseCallback_enforcePermission(); + Objects.requireNonNull(cb); mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb); } @@ -9267,6 +9319,8 @@ public class AudioService extends IAudioService.Stub /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */ public void unregisterHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { + super.unregisterHeadToSoundstagePoseCallback_enforcePermission(); + Objects.requireNonNull(cb); mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb); } @@ -9274,12 +9328,16 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getSpatializerCompatibleAudioDevices() */ public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() { + super.getSpatializerCompatibleAudioDevices_enforcePermission(); + return mSpatializerHelper.getCompatibleAudioDevices(); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + super.addSpatializerCompatibleAudioDevice_enforcePermission(); + Objects.requireNonNull(ada); mSpatializerHelper.addCompatibleAudioDevice(ada); } @@ -9287,6 +9345,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { + super.removeSpatializerCompatibleAudioDevice_enforcePermission(); + Objects.requireNonNull(ada); mSpatializerHelper.removeCompatibleAudioDevice(ada); } @@ -9294,24 +9354,32 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getSupportedHeadTrackingModes() */ public int[] getSupportedHeadTrackingModes() { + super.getSupportedHeadTrackingModes_enforcePermission(); + return mSpatializerHelper.getSupportedHeadTrackingModes(); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getHeadTrackingMode() */ public int getActualHeadTrackingMode() { + super.getActualHeadTrackingMode_enforcePermission(); + return mSpatializerHelper.getActualHeadTrackingMode(); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getDesiredHeadTrackingMode() */ public int getDesiredHeadTrackingMode() { + super.getDesiredHeadTrackingMode_enforcePermission(); + return mSpatializerHelper.getDesiredHeadTrackingMode(); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setGlobalTransform */ public void setSpatializerGlobalTransform(@NonNull float[] transform) { + super.setSpatializerGlobalTransform_enforcePermission(); + Objects.requireNonNull(transform); mSpatializerHelper.setGlobalTransform(transform); } @@ -9319,12 +9387,16 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#recenterHeadTracker() */ public void recenterHeadTracker() { + super.recenterHeadTracker_enforcePermission(); + mSpatializerHelper.recenterHeadTracker(); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setDesiredHeadTrackingMode */ public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { + super.setDesiredHeadTrackingMode_enforcePermission(); + switch(mode) { case Spatializer.HEAD_TRACKING_MODE_DISABLED: case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: @@ -9339,6 +9411,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setEffectParameter */ public void setSpatializerParameter(int key, @NonNull byte[] value) { + super.setSpatializerParameter_enforcePermission(); + Objects.requireNonNull(value); mSpatializerHelper.setEffectParameter(key, value); } @@ -9346,6 +9420,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getEffectParameter */ public void getSpatializerParameter(int key, @NonNull byte[] value) { + super.getSpatializerParameter_enforcePermission(); + Objects.requireNonNull(value); mSpatializerHelper.getEffectParameter(key, value); } @@ -9353,12 +9429,16 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getOutput */ public int getSpatializerOutput() { + super.getSpatializerOutput_enforcePermission(); + return mSpatializerHelper.getOutput(); } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setOnSpatializerOutputChangedListener */ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) { + super.registerSpatializerOutputCallback_enforcePermission(); + Objects.requireNonNull(cb); mSpatializerHelper.registerSpatializerOutputCallback(cb); } @@ -9366,6 +9446,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#clearOnSpatializerOutputChangedListener */ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) { + super.unregisterSpatializerOutputCallback_enforcePermission(); + Objects.requireNonNull(cb); mSpatializerHelper.unregisterSpatializerOutputCallback(cb); } @@ -9483,6 +9565,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioManager#getMutingExpectedDevice */ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() { + super.getMutingExpectedDevice_enforcePermission(); + synchronized (mMuteAwaitConnectionLock) { return mMutingExpectedDevice; } @@ -9524,6 +9608,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#registerMuteAwaitConnectionCallback */ public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb, boolean register) { + super.registerMuteAwaitConnectionDispatcher_enforcePermission(); + if (register) { mMuteAwaitConnectionDispatchers.register(cb); } else { @@ -11049,6 +11135,8 @@ public class AudioService extends IAudioService.Stub @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) /** @see AudioPolicy#getFocusStack() */ public List<AudioFocusInfo> getFocusStack() { + super.getFocusStack_enforcePermission(); + return mMediaFocusControl.getFocusStack(); } @@ -11826,6 +11914,8 @@ public class AudioService extends IAudioService.Stub // Multi Audio Focus //====================== public void setMultiAudioFocusEnabled(boolean enabled) { + super.setMultiAudioFocusEnabled_enforcePermission(); + if (mMediaFocusControl != null) { boolean mafEnabled = mMediaFocusControl.getMultiAudioFocusEnabled(); if (mafEnabled != enabled) { @@ -11928,6 +12018,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#addAssistantServicesUids(int []) */ @Override public void addAssistantServicesUids(int [] assistantUids) { + super.addAssistantServicesUids_enforcePermission(); + Objects.requireNonNull(assistantUids); synchronized (mSettingsLock) { @@ -11939,6 +12031,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#removeAssistantServicesUids(int []) */ @Override public void removeAssistantServicesUids(int [] assistantUids) { + super.removeAssistantServicesUids_enforcePermission(); + Objects.requireNonNull(assistantUids); synchronized (mSettingsLock) { removeAssistantServiceUidsLocked(assistantUids); @@ -11949,6 +12043,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getAssistantServicesUids() */ @Override public int[] getAssistantServicesUids() { + super.getAssistantServicesUids_enforcePermission(); + int [] assistantUids; synchronized (mSettingsLock) { assistantUids = mAssistantUids.stream().mapToInt(Integer::intValue).toArray(); @@ -11960,6 +12056,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setActiveAssistantServiceUids(int []) */ @Override public void setActiveAssistantServiceUids(int [] activeAssistantUids) { + super.setActiveAssistantServiceUids_enforcePermission(); + Objects.requireNonNull(activeAssistantUids); synchronized (mSettingsLock) { mActiveAssistantServiceUids = activeAssistantUids; @@ -11971,6 +12069,8 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getActiveAssistantServiceUids() */ @Override public int[] getActiveAssistantServiceUids() { + super.getActiveAssistantServiceUids_enforcePermission(); + int [] activeAssistantUids; synchronized (mSettingsLock) { activeAssistantUids = mActiveAssistantServiceUids.clone(); diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 74bfa80e4704..0bc4b20b4643 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -17,12 +17,12 @@ package com.android.server.audio; import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE; -import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_CLIENT_VOLUME; -import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_MASTER; -import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_PLAYBACK_RESTRICTED; -import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_MUTED; -import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_VOLUME; -import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_VOLUME_SHAPER; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER; import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID; import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED; @@ -1155,22 +1155,22 @@ public final class PlaybackActivityMonitor if (mEventValue <= 0) { builder.append("none "); } else { - if ((mEventValue & PLAYER_MUTE_MASTER) != 0) { + if ((mEventValue & MUTED_BY_MASTER) != 0) { builder.append("masterMute "); } - if ((mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0) { + if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) { builder.append("streamVolume "); } - if ((mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0) { + if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) { builder.append("streamMute "); } - if ((mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) { - builder.append("playbackRestricted "); + if ((mEventValue & MUTED_BY_APP_OPS) != 0) { + builder.append("appOps "); } - if ((mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0) { + if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) { builder.append("clientVolume "); } - if ((mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0) { + if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) { builder.append("volumeShaper "); } } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index d2016c473bf4..4358ee2f05f4 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -178,6 +178,8 @@ public class AuthService extends SystemService { public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) throws RemoteException { + super.createTestSession_enforcePermission(); + final long identity = Binder.clearCallingIdentity(); try { return mInjector.getBiometricService() @@ -192,6 +194,8 @@ public class AuthService extends SystemService { public List<SensorPropertiesInternal> getSensorProperties(String opPackageName) throws RemoteException { + super.getSensorProperties_enforcePermission(); + final long identity = Binder.clearCallingIdentity(); try { // Get the result from BiometricService, since it is the source of truth for all @@ -206,6 +210,8 @@ public class AuthService extends SystemService { @Override public String getUiPackage() { + super.getUiPackage_enforcePermission(); + return getContext().getResources() .getString(R.string.config_biometric_prompt_ui_package); } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index c29755aaa845..cd30d26b69ad 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -495,6 +495,8 @@ public class BiometricService extends SystemService { public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) throws RemoteException { + super.createTestSession_enforcePermission(); + for (BiometricSensor sensor : mSensors) { if (sensor.id == sensorId) { return sensor.impl.createTestSession(callback, opPackageName); @@ -510,6 +512,8 @@ public class BiometricService extends SystemService { public List<SensorPropertiesInternal> getSensorProperties(String opPackageName) throws RemoteException { + super.getSensorProperties_enforcePermission(); + final List<SensorPropertiesInternal> sensors = new ArrayList<>(); for (BiometricSensor sensor : mSensors) { // Explicitly re-create as the super class, since AIDL doesn't play nicely with @@ -526,6 +530,8 @@ public class BiometricService extends SystemService { @Override // Binder call public void onReadyForAuthentication(long requestId, int cookie) { + super.onReadyForAuthentication_enforcePermission(); + mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie)); } @@ -534,6 +540,8 @@ public class BiometricService extends SystemService { public long authenticate(IBinder token, long operationId, int userId, IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) { + super.authenticate_enforcePermission(); + if (token == null || receiver == null || opPackageName == null || promptInfo == null) { Slog.e(TAG, "Unable to authenticate, one or more null arguments"); return -1; @@ -564,6 +572,8 @@ public class BiometricService extends SystemService { @Override // Binder call public void cancelAuthentication(IBinder token, String opPackageName, long requestId) { + super.cancelAuthentication_enforcePermission(); + SomeArgs args = SomeArgs.obtain(); args.arg1 = token; args.arg2 = opPackageName; @@ -577,6 +587,8 @@ public class BiometricService extends SystemService { public int canAuthenticate(String opPackageName, int userId, int callingUserId, @Authenticators.Types int authenticators) { + super.canAuthenticate_enforcePermission(); + Slog.d(TAG, "canAuthenticate: User=" + userId + ", Caller=" + callingUserId + ", Authenticators=" + authenticators); @@ -599,6 +611,8 @@ public class BiometricService extends SystemService { @Override public boolean hasEnrolledBiometrics(int userId, String opPackageName) { + super.hasEnrolledBiometrics_enforcePermission(); + try { for (BiometricSensor sensor : mSensors) { if (sensor.impl.hasEnrolledTemplates(userId, opPackageName)) { @@ -618,6 +632,8 @@ public class BiometricService extends SystemService { @Authenticators.Types int strength, @NonNull IBiometricAuthenticator authenticator) { + super.registerAuthenticator_enforcePermission(); + Slog.d(TAG, "Registering ID: " + id + " Modality: " + modality + " Strength: " + strength); @@ -664,6 +680,8 @@ public class BiometricService extends SystemService { public void registerEnabledOnKeyguardCallback( IBiometricEnabledOnKeyguardCallback callback, int callingUserId) { + super.registerEnabledOnKeyguardCallback_enforcePermission(); + mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback)); try { callback.onChanged(mSettingObserver.getEnabledOnKeyguard(callingUserId), @@ -678,6 +696,8 @@ public class BiometricService extends SystemService { public void invalidateAuthenticatorIds(int userId, int fromSensorId, IInvalidationCallback callback) { + super.invalidateAuthenticatorIds_enforcePermission(); + InvalidationTracker.start(getContext(), mSensors, userId, fromSensorId, callback); } @@ -685,6 +705,8 @@ public class BiometricService extends SystemService { @Override // Binder call public long[] getAuthenticatorIds(int callingUserId) { + super.getAuthenticatorIds_enforcePermission(); + final List<Long> authenticatorIds = new ArrayList<>(); for (BiometricSensor sensor : mSensors) { try { @@ -717,6 +739,8 @@ public class BiometricService extends SystemService { int userId, byte[] hardwareAuthToken) { // Check originating strength + super.resetLockoutTimeBound_enforcePermission(); + if (!Utils.isAtLeastStrength(getSensorForId(fromSensorId).getCurrentStrength(), Authenticators.BIOMETRIC_STRONG)) { Slog.w(TAG, "Sensor: " + fromSensorId + " is does not meet the required strength to" @@ -754,6 +778,8 @@ public class BiometricService extends SystemService { @Override // Binder call public int getCurrentStrength(int sensorId) { + super.getCurrentStrength_enforcePermission(); + for (BiometricSensor sensor : mSensors) { if (sensor.id == sensorId) { return sensor.getCurrentStrength(); @@ -772,6 +798,8 @@ public class BiometricService extends SystemService { @Authenticators.Types int authenticators) { + super.getCurrentModality_enforcePermission(); + Slog.d(TAG, "getCurrentModality: User=" + userId + ", Caller=" + callingUserId + ", Authenticators=" + authenticators); @@ -794,6 +822,8 @@ public class BiometricService extends SystemService { @Override // Binder call public int getSupportedModalities(@Authenticators.Types int authenticators) { + super.getSupportedModalities_enforcePermission(); + Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators); if (!Utils.isValidAuthenticatorConfig(authenticators)) { diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index 1d90954c44f6..055c63dd7e69 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -38,8 +38,7 @@ import java.util.function.Supplier; * Abstract {@link HalClientMonitor} subclass that operations eligible/interested in acquisition * messages should extend. */ -public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implements Interruptable, - ErrorConsumer { +public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implements ErrorConsumer { private static final String TAG = "Biometrics/AcquisitionClient"; @@ -217,4 +216,9 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); } } + + @Override + public boolean isInterruptable() { + return true; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index da7781add8c6..0216e49b531b 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -193,9 +193,9 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { } // If the current client dies we should cancel the current operation. - if (this instanceof Interruptable) { + if (this.isInterruptable()) { Slog.e(TAG, "Binder died, cancelling client"); - ((Interruptable) this).cancel(); + this.cancel(); } mToken = null; if (clearListener) { @@ -320,4 +320,12 @@ public abstract class BaseClientMonitor implements IBinder.DeathRecipient { } callback.onClientFinished(this, true /* success */); } + + /** + * Checks if other client monitor can interrupt current client monitor + * @return if current client can be interrupted + */ + public boolean isInterruptable() { + return false; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java index dacec38b0e7e..4825f1dea66f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java @@ -46,7 +46,7 @@ public class BiometricSchedulerOperation { /** * The operation is added to the list of pending operations, but a subsequent operation - * has been added. This state only applies to {@link Interruptable} operations. When this + * has been added. This state only applies to interruptable operations. When this * operation reaches the head of the queue, it will send ERROR_CANCELED and finish. */ protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1; @@ -347,9 +347,9 @@ public class BiometricSchedulerOperation { return mClientMonitor == clientMonitor; } - /** If this operation is {@link Interruptable}. */ + /** If this operation is interruptable. */ public boolean isInterruptable() { - return mClientMonitor instanceof Interruptable; + return mClientMonitor.isInterruptable(); } private boolean isHalOperation() { diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java deleted file mode 100644 index 4f645efcccf0..000000000000 --- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.biometrics.sensors; - -import android.annotation.NonNull; - -/** - * Interface that {@link BaseClientMonitor} subclasses eligible for cancellation should implement. - */ -public interface Interruptable { - /** - * Requests to end the ClientMonitor's lifecycle. - */ - void cancel(); - - /** - * Notifies the client that it needs to finish before - * {@link BaseClientMonitor#start(ClientMonitorCallback)} was invoked. This usually happens - * if the client is still waiting in the pending queue and got notified that a subsequent - * operation is preempting it. - * - * This method must invoke - * {@link ClientMonitorCallback#onClientFinished(BaseClientMonitor, boolean)} on the - * given callback (with success). - * - * @param callback invoked when the operation is completed. - */ - void cancelWithoutStarting(@NonNull ClientMonitorCallback callback); -} diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 7a5b58413014..33d3b647056a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java @@ -99,6 +99,8 @@ public class FaceService extends SystemService { @Override public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) { + super.createTestSession_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { @@ -112,6 +114,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) { + super.dumpSensorServiceStateProto_enforcePermission(); + final ProtoOutputStream proto = new ProtoOutputStream(); final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider != null) { @@ -125,6 +129,8 @@ public class FaceService extends SystemService { @Override // Binder call public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal( String opPackageName) { + super.getSensorPropertiesInternal_enforcePermission(); + return mRegistry.getAllProperties(); } @@ -132,6 +138,8 @@ public class FaceService extends SystemService { @Override // Binder call public FaceSensorPropertiesInternal getSensorProperties(int sensorId, @NonNull String opPackageName) { + super.getSensorProperties_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId @@ -146,6 +154,8 @@ public class FaceService extends SystemService { @Override // Binder call public void generateChallenge(IBinder token, int sensorId, int userId, IFaceServiceReceiver receiver, String opPackageName) { + super.generateChallenge_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId); @@ -159,6 +169,8 @@ public class FaceService extends SystemService { @Override // Binder call public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge) { + super.revokeChallenge_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId); @@ -173,6 +185,8 @@ public class FaceService extends SystemService { public long enroll(int userId, final IBinder token, final byte[] hardwareAuthToken, final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures, Surface previewSurface, boolean debugConsent) { + super.enroll_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); @@ -201,12 +215,16 @@ public class FaceService extends SystemService { final IFaceServiceReceiver receiver, final String opPackageName, final int[] disabledFeatures) { // TODO(b/145027036): Implement this. + super.enrollRemotely_enforcePermission(); + return -1; } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC) @Override // Binder call public void cancelEnrollment(final IBinder token, long requestId) { + super.cancelEnrollment_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelEnrollment"); @@ -224,6 +242,8 @@ public class FaceService extends SystemService { // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or // lockdown, something wrong happened. See similar path in FingerprintService. + super.authenticate_enforcePermission(); + final boolean restricted = false; // Face APIs are private final int statsClient = Utils.isKeyguard(getContext(), opPackageName) ? BiometricsProtoEnums.CLIENT_KEYGUARD @@ -249,6 +269,8 @@ public class FaceService extends SystemService { @Override // Binder call public long detectFace(final IBinder token, final int userId, final IFaceServiceReceiver receiver, final String opPackageName) { + super.detectFace_enforcePermission(); + if (!Utils.isKeyguard(getContext(), opPackageName)) { Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName); return -1; @@ -278,6 +300,8 @@ public class FaceService extends SystemService { IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) { + super.prepareForAuthentication_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for prepareForAuthentication"); @@ -295,6 +319,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public void startPreparedClient(int sensorId, int cookie) { + super.startPreparedClient_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for startPreparedClient"); @@ -308,6 +334,8 @@ public class FaceService extends SystemService { @Override // Binder call public void cancelAuthentication(final IBinder token, final String opPackageName, final long requestId) { + super.cancelAuthentication_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelAuthentication"); @@ -321,6 +349,8 @@ public class FaceService extends SystemService { @Override // Binder call public void cancelFaceDetect(final IBinder token, final String opPackageName, final long requestId) { + super.cancelFaceDetect_enforcePermission(); + if (!Utils.isKeyguard(getContext(), opPackageName)) { Slog.w(TAG, "cancelFaceDetect called from non-sysui package: " + opPackageName); @@ -340,6 +370,8 @@ public class FaceService extends SystemService { @Override // Binder call public void cancelAuthenticationFromService(int sensorId, final IBinder token, final String opPackageName, final long requestId) { + super.cancelAuthenticationFromService_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for cancelAuthenticationFromService"); @@ -353,6 +385,8 @@ public class FaceService extends SystemService { @Override // Binder call public void remove(final IBinder token, final int faceId, final int userId, final IFaceServiceReceiver receiver, final String opPackageName) { + super.remove_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for remove"); @@ -367,6 +401,8 @@ public class FaceService extends SystemService { @Override // Binder call public void removeAll(final IBinder token, final int userId, final IFaceServiceReceiver receiver, final String opPackageName) { + super.removeAll_enforcePermission(); + final FaceServiceReceiver internalReceiver = new FaceServiceReceiver() { int sensorsFinishedRemoving = 0; final int numSensors = getSensorPropertiesInternal( @@ -399,6 +435,8 @@ public class FaceService extends SystemService { @Override // Binder call public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback, final String opPackageName) { + super.addLockoutResetCallback_enforcePermission(); + mLockoutResetDispatcher.addCallback(callback, opPackageName); } @@ -458,6 +496,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public boolean isHardwareDetected(int sensorId, String opPackageName) { + super.isHardwareDetected_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); @@ -474,6 +514,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public List<Face> getEnrolledFaces(int sensorId, int userId, String opPackageName) { + super.getEnrolledFaces_enforcePermission(); + if (userId != UserHandle.getCallingUserId()) { Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS); } @@ -490,6 +532,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) { + super.hasEnrolledFaces_enforcePermission(); + if (userId != UserHandle.getCallingUserId()) { Utils.checkPermission(getContext(), INTERACT_ACROSS_USERS); } @@ -506,6 +550,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) { + super.getLockoutModeForUser_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getLockoutModeForUser"); @@ -519,6 +565,8 @@ public class FaceService extends SystemService { @Override public void invalidateAuthenticatorId(int sensorId, int userId, IInvalidationCallback callback) { + super.invalidateAuthenticatorId_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for invalidateAuthenticatorId"); @@ -531,6 +579,8 @@ public class FaceService extends SystemService { @Override // Binder call public long getAuthenticatorId(int sensorId, int userId) { + super.getAuthenticatorId_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getAuthenticatorId"); @@ -544,6 +594,8 @@ public class FaceService extends SystemService { @Override // Binder call public void resetLockout(IBinder token, int sensorId, int userId, byte[] hardwareAuthToken, String opPackageName) { + super.resetLockout_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName); @@ -558,6 +610,8 @@ public class FaceService extends SystemService { public void setFeature(final IBinder token, int userId, int feature, boolean enabled, final byte[] hardwareAuthToken, IFaceServiceReceiver receiver, final String opPackageName) { + super.setFeature_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for setFeature"); @@ -572,6 +626,8 @@ public class FaceService extends SystemService { @Override public void getFeature(final IBinder token, int userId, int feature, IFaceServiceReceiver receiver, final String opPackageName) { + super.getFeature_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for getFeature"); @@ -615,6 +671,8 @@ public class FaceService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) public void registerAuthenticators( @NonNull List<FaceSensorPropertiesInternal> hidlSensors) { + super.registerAuthenticators_enforcePermission(); + mRegistry.registerAll(() -> { final List<ServiceProvider> providers = new ArrayList<>(); for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) { diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java index cfbb5dce4c2b..7a13c91a2cf1 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java @@ -139,6 +139,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void setTestHalEnabled(boolean enabled) { + super.setTestHalEnabled_enforcePermission(); + mProvider.setTestHalEnabled(enabled); mSensor.setTestHalEnabled(enabled); } @@ -147,6 +149,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void startEnroll(int userId) { + super.startEnroll_enforcePermission(); + mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, mContext.getOpPackageName(), new int[0] /* disabledFeatures */, null /* previewSurface */, false /* debugConsent */); @@ -156,6 +160,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void finishEnroll(int userId) { + super.finishEnroll_enforcePermission(); + int nextRandomId = mRandom.nextInt(); while (mEnrollmentIds.contains(nextRandomId)) { nextRandomId = mRandom.nextInt(); @@ -171,6 +177,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void acceptAuthentication(int userId) { // Fake authentication with any of the existing faces + super.acceptAuthentication_enforcePermission(); + List<Face> faces = FaceUtils.getInstance(mSensorId) .getBiometricsForUser(mContext, userId); if (faces.isEmpty()) { @@ -186,6 +194,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void rejectAuthentication(int userId) { + super.rejectAuthentication_enforcePermission(); + mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed(); } @@ -194,6 +204,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyAcquired(int userId, int acquireInfo) { + super.notifyAcquired_enforcePermission(); + BaseFrame data = new BaseFrame(); data.acquiredInfo = (byte) acquireInfo; @@ -210,6 +222,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyError(int userId, int errorCode) { + super.notifyError_enforcePermission(); + mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode, 0 /* vendorCode */); } @@ -218,6 +232,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void cleanupInternalState(int userId) { + super.cleanupInternalState_enforcePermission(); + Slog.d(TAG, "cleanupInternalState: " + userId); mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index 800d4b8acf61..0f5cdc3e1cf8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -59,7 +59,6 @@ import com.android.server.biometrics.sensors.BaseClientMonitor; import com.android.server.biometrics.sensors.BiometricScheduler; import com.android.server.biometrics.sensors.EnumerateConsumer; import com.android.server.biometrics.sensors.ErrorConsumer; -import com.android.server.biometrics.sensors.Interruptable; import com.android.server.biometrics.sensors.LockoutCache; import com.android.server.biometrics.sensors.LockoutConsumer; import com.android.server.biometrics.sensors.LockoutResetDispatcher; @@ -638,7 +637,7 @@ public class Sensor { public void onBinderDied() { final BaseClientMonitor client = mScheduler.getCurrentClient(); - if (client instanceof Interruptable) { + if (client.isInterruptable()) { Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client); final ErrorConsumer errorConsumer = (ErrorConsumer) client; errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE, diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java index 7a6a274f8dd7..151ffaa1cb28 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/BiometricTestSessionImpl.java @@ -130,6 +130,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void setTestHalEnabled(boolean enabled) { + super.setTestHalEnabled_enforcePermission(); + mFace10.setTestHalEnabled(enabled); } @@ -137,6 +139,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void startEnroll(int userId) { + super.startEnroll_enforcePermission(); + mFace10.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, mContext.getOpPackageName(), new int[0] /* disabledFeatures */, null /* previewSurface */, false /* debugConsent */); @@ -146,6 +150,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void finishEnroll(int userId) { + super.finishEnroll_enforcePermission(); + int nextRandomId = mRandom.nextInt(); while (mEnrollmentIds.contains(nextRandomId)) { nextRandomId = mRandom.nextInt(); @@ -161,6 +167,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void acceptAuthentication(int userId) { // Fake authentication with any of the existing fingers + super.acceptAuthentication_enforcePermission(); + List<Face> faces = FaceUtils.getLegacyInstance(mSensorId) .getBiometricsForUser(mContext, userId); if (faces.isEmpty()) { @@ -176,6 +184,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void rejectAuthentication(int userId) { + super.rejectAuthentication_enforcePermission(); + mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* faceId */, userId, null); } @@ -183,6 +193,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyAcquired(int userId, int acquireInfo) { + super.notifyAcquired_enforcePermission(); + mHalResultController.onAcquired(0 /* deviceId */, userId, acquireInfo, 0 /* vendorCode */); } @@ -190,6 +202,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyError(int userId, int errorCode) { + super.notifyError_enforcePermission(); + mHalResultController.onError(0 /* deviceId */, userId, errorCode, 0 /* vendorCode */); } @@ -197,6 +211,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void cleanupInternalState(int userId) { + super.cleanupInternalState_enforcePermission(); + mFace10.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 156e6bb503ec..70470e90b398 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -133,6 +133,8 @@ public class FingerprintService extends SystemService { @Override public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback, @NonNull String opPackageName) { + super.createTestSession_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { @@ -146,6 +148,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer) { + super.dumpSensorServiceStateProto_enforcePermission(); + final ProtoOutputStream proto = new ProtoOutputStream(); final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider != null) { @@ -169,6 +173,8 @@ public class FingerprintService extends SystemService { @Override public FingerprintSensorPropertiesInternal getSensorProperties(int sensorId, @NonNull String opPackageName) { + super.getSensorProperties_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for getSensorProperties, sensorId: " + sensorId @@ -182,6 +188,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void generateChallenge(IBinder token, int sensorId, int userId, IFingerprintServiceReceiver receiver, String opPackageName) { + super.generateChallenge_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for generateChallenge, sensorId: " + sensorId); @@ -195,6 +203,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge) { + super.revokeChallenge_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching sensor for revokeChallenge, sensorId: " + sensorId); @@ -210,6 +220,8 @@ public class FingerprintService extends SystemService { public long enroll(final IBinder token, @NonNull final byte[] hardwareAuthToken, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName, @FingerprintManager.EnrollReason int enrollReason) { + super.enroll_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for enroll"); @@ -223,6 +235,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @Override // Binder call public void cancelEnrollment(final IBinder token, long requestId) { + super.cancelEnrollment_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for cancelEnrollment"); @@ -399,6 +413,8 @@ public class FingerprintService extends SystemService { @Override public long detectFingerprint(final IBinder token, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName) { + super.detectFingerprint_enforcePermission(); + if (!Utils.isKeyguard(getContext(), opPackageName)) { Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName); return -1; @@ -427,6 +443,8 @@ public class FingerprintService extends SystemService { public void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication) { + super.prepareForAuthentication_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for prepareForAuthentication"); @@ -443,6 +461,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_BIOMETRIC) @Override // Binder call public void startPreparedClient(int sensorId, int cookie) { + super.startPreparedClient_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for startPreparedClient"); @@ -486,6 +506,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void cancelFingerprintDetect(final IBinder token, final String opPackageName, final long requestId) { + super.cancelFingerprintDetect_enforcePermission(); + if (!Utils.isKeyguard(getContext(), opPackageName)) { Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: " + opPackageName); @@ -507,6 +529,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void cancelAuthenticationFromService(final int sensorId, final IBinder token, final String opPackageName, final long requestId) { + super.cancelAuthenticationFromService_enforcePermission(); + Slog.d(TAG, "cancelAuthenticationFromService, sensorId: " + sensorId); final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); @@ -522,6 +546,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void remove(final IBinder token, final int fingerId, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName) { + super.remove_enforcePermission(); + final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for remove"); @@ -536,6 +562,8 @@ public class FingerprintService extends SystemService { public void removeAll(final IBinder token, final int userId, final IFingerprintServiceReceiver receiver, final String opPackageName) { + super.removeAll_enforcePermission(); + final FingerprintServiceReceiver internalReceiver = new FingerprintServiceReceiver() { int sensorsFinishedRemoving = 0; final int numSensors = getSensorPropertiesInternal( @@ -568,6 +596,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void addLockoutResetCallback(final IBiometricServiceLockoutResetCallback callback, final String opPackageName) { + super.addLockoutResetCallback_enforcePermission(); + mLockoutResetDispatcher.addCallback(callback, opPackageName); } @@ -651,6 +681,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public boolean isHardwareDetected(int sensorId, String opPackageName) { + super.isHardwareDetected_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for isHardwareDetected, caller: " + opPackageName); @@ -663,6 +695,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @Override // Binder call public void rename(final int fingerId, final int userId, final String name) { + super.rename_enforcePermission(); + if (!Utils.isCurrentUserOrProfile(getContext(), userId)) { return; } @@ -718,6 +752,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) { + super.hasEnrolledFingerprints_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for hasEnrolledFingerprints, caller: " + opPackageName); @@ -730,6 +766,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public @LockoutTracker.LockoutMode int getLockoutModeForUser(int sensorId, int userId) { + super.getLockoutModeForUser_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getLockoutModeForUser"); @@ -742,6 +780,8 @@ public class FingerprintService extends SystemService { @Override public void invalidateAuthenticatorId(int sensorId, int userId, IInvalidationCallback callback) { + super.invalidateAuthenticatorId_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for invalidateAuthenticatorId"); @@ -753,6 +793,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override // Binder call public long getAuthenticatorId(int sensorId, int userId) { + super.getAuthenticatorId_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for getAuthenticatorId"); @@ -765,6 +807,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void resetLockout(IBinder token, int sensorId, int userId, @Nullable byte[] hardwareAuthToken, String opPackageName) { + super.resetLockout_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "Null provider for resetLockout, caller: " + opPackageName); @@ -777,18 +821,24 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @Override public boolean isClientActive() { + super.isClientActive_enforcePermission(); + return mGestureAvailabilityDispatcher.isAnySensorActive(); } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @Override public void addClientActiveCallback(IFingerprintClientActiveCallback callback) { + super.addClientActiveCallback_enforcePermission(); + mGestureAvailabilityDispatcher.registerCallback(callback); } @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_FINGERPRINT) @Override public void removeClientActiveCallback(IFingerprintClientActiveCallback callback) { + super.removeClientActiveCallback_enforcePermission(); + mGestureAvailabilityDispatcher.removeCallback(callback); } @@ -796,6 +846,8 @@ public class FingerprintService extends SystemService { @Override // Binder call public void registerAuthenticators( @NonNull List<FingerprintSensorPropertiesInternal> hidlSensors) { + super.registerAuthenticators_enforcePermission(); + mRegistry.registerAll(() -> { final List<ServiceProvider> providers = new ArrayList<>(); providers.addAll(getHidlProviders(hidlSensors)); @@ -814,12 +866,16 @@ public class FingerprintService extends SystemService { @Override public void addAuthenticatorsRegisteredCallback( IFingerprintAuthenticatorsRegisteredCallback callback) { + super.addAuthenticatorsRegisteredCallback_enforcePermission(); + mRegistry.addAllRegisteredCallback(callback); } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) { + super.registerBiometricStateListener_enforcePermission(); + mBiometricStateCallback.registerBiometricStateListener(listener); } @@ -827,6 +883,8 @@ public class FingerprintService extends SystemService { @Override public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { + super.onPointerDown_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching provider for onFingerDown, sensorId: " + sensorId); @@ -838,6 +896,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onPointerUp(long requestId, int sensorId) { + super.onPointerUp_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching provider for onFingerUp, sensorId: " + sensorId); @@ -849,6 +909,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onUiReady(long requestId, int sensorId) { + super.onUiReady_enforcePermission(); + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); if (provider == null) { Slog.w(TAG, "No matching provider for onUiReady, sensorId: " + sensorId); @@ -860,6 +922,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) { + super.setUdfpsOverlayController_enforcePermission(); + for (ServiceProvider provider : mRegistry.getProviders()) { provider.setUdfpsOverlayController(controller); } @@ -868,6 +932,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void setSidefpsController(@NonNull ISidefpsController controller) { + super.setSidefpsController_enforcePermission(); + for (ServiceProvider provider : mRegistry.getProviders()) { provider.setSidefpsController(controller); } @@ -884,6 +950,8 @@ public class FingerprintService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override public void onPowerPressed() { + super.onPowerPressed_enforcePermission(); + for (ServiceProvider provider : mRegistry.getProviders()) { provider.onPowerPressed(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index 4181b99f9aba..135eccf9026a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -135,6 +135,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void setTestHalEnabled(boolean enabled) { + super.setTestHalEnabled_enforcePermission(); + mProvider.setTestHalEnabled(enabled); mSensor.setTestHalEnabled(enabled); } @@ -143,6 +145,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void startEnroll(int userId) { + super.startEnroll_enforcePermission(); + mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL); } @@ -151,6 +155,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void finishEnroll(int userId) { + super.finishEnroll_enforcePermission(); + int nextRandomId = mRandom.nextInt(); while (mEnrollmentIds.contains(nextRandomId)) { nextRandomId = mRandom.nextInt(); @@ -166,6 +172,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { public void acceptAuthentication(int userId) { // Fake authentication with any of the existing fingers + super.acceptAuthentication_enforcePermission(); + List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId) .getBiometricsForUser(mContext, userId); if (fingerprints.isEmpty()) { @@ -181,6 +189,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void rejectAuthentication(int userId) { + super.rejectAuthentication_enforcePermission(); + mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed(); } @@ -188,6 +198,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyAcquired(int userId, int acquireInfo) { + super.notifyAcquired_enforcePermission(); + mSensor.getSessionForUser(userId).getHalSessionCallback() .onAcquired((byte) acquireInfo, 0 /* vendorCode */); } @@ -196,6 +208,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyError(int userId, int errorCode) { + super.notifyError_enforcePermission(); + mSensor.getSessionForUser(userId).getHalSessionCallback().onError((byte) errorCode, 0 /* vendorCode */); } @@ -204,6 +218,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void cleanupInternalState(int userId) { + super.cleanupInternalState_enforcePermission(); + Slog.d(TAG, "cleanupInternalState: " + userId); mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 7f1fb1cfc4bd..49bd44b24f07 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -72,7 +72,6 @@ import java.util.function.Supplier; class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements Udfps, LockoutConsumer, PowerPressHandler { private static final String TAG = "FingerprintAuthenticationClient"; - private static final int MESSAGE_IGNORE_AUTH = 1; private static final int MESSAGE_AUTH_SUCCESS = 2; private static final int MESSAGE_FINGER_UP = 3; @NonNull @@ -249,12 +248,6 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> () -> { long delay = 0; if (authenticated && mSensorProps.isAnySidefpsType()) { - if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) { - Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press"); - onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, - 0, true); - return; - } delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp; if (mSideFpsLastAcquireStartTime != -1) { @@ -515,18 +508,15 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> if (mSensorProps.isAnySidefpsType()) { Slog.i(TAG, "(sideFPS): onPowerPressed"); mHandler.post(() -> { - if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) { - Slog.i(TAG, "(sideFPS): Ignoring auth in queue"); - mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); - // Do not call onError() as that will send an additional callback to coex. - onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true); - mAuthSessionCoordinator.authEndedFor(getTargetUserId(), - mBiometricStrength, getSensorId(), getRequestId()); - } - mHandler.removeMessages(MESSAGE_IGNORE_AUTH); - mHandler.postDelayed(() -> { - }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor); - + Slog.i(TAG, "(sideFPS): finishing auth"); + // Ignore auths after a power has been detected + mHandler.removeMessages(MESSAGE_AUTH_SUCCESS); + // Do not call onError() as that will send an additional callback to coex. + onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, + 0, true); + mSensorOverlays.hide(getSensorId()); + mAuthSessionCoordinator.authEndedFor(getTargetUserId(), + mBiometricStrength, getSensorId(), getRequestId()); }); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java index 682c00536e10..86a9f7998398 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java @@ -136,6 +136,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void setTestHalEnabled(boolean enabled) { + super.setTestHalEnabled_enforcePermission(); + mFingerprint21.setTestHalEnabled(enabled); } @@ -143,6 +145,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void startEnroll(int userId) { + super.startEnroll_enforcePermission(); + mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver, mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL); } @@ -151,6 +155,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void finishEnroll(int userId) { + super.finishEnroll_enforcePermission(); + int nextRandomId = mRandom.nextInt(); while (mEnrollmentIds.contains(nextRandomId)) { nextRandomId = mRandom.nextInt(); @@ -166,6 +172,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { public void acceptAuthentication(int userId) { // Fake authentication with any of the existing fingers + super.acceptAuthentication_enforcePermission(); + List<Fingerprint> fingerprints = FingerprintUtils.getLegacyInstance(mSensorId) .getBiometricsForUser(mContext, userId); if (fingerprints.isEmpty()) { @@ -181,6 +189,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void rejectAuthentication(int userId) { + super.rejectAuthentication_enforcePermission(); + mHalResultController.onAuthenticated(0 /* deviceId */, 0 /* fingerId */, userId, null); } @@ -188,6 +198,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyAcquired(int userId, int acquireInfo) { + super.notifyAcquired_enforcePermission(); + mHalResultController.onAcquired(0 /* deviceId */, acquireInfo, 0 /* vendorCode */); } @@ -195,6 +207,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void notifyError(int userId, int errorCode) { + super.notifyError_enforcePermission(); + mHalResultController.onError(0 /* deviceId */, errorCode, 0 /* vendorCode */); } @@ -202,6 +216,8 @@ public class BiometricTestSessionImpl extends ITestSession.Stub { @Override public void cleanupInternalState(int userId) { + super.cleanupInternalState_enforcePermission(); + mFingerprint21.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java index ff1e7628c0bd..35ea36c5d56f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java +++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java @@ -63,6 +63,8 @@ public class IrisService extends SystemService { // to wait, and some of the operations below might take a significant amount of time to // complete (calls to the HALs). To avoid blocking the rest of system server we put // this on a background thread. + super.registerAuthenticators_enforcePermission(); + final ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */); thread.start(); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 11eb78277beb..b882c47b71d0 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -841,6 +841,7 @@ public class CameraServiceProxy extends SystemService streamProtos[i].histogramCounts = streamStats.getHistogramCounts(); streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile(); streamProtos[i].streamUseCase = streamStats.getStreamUseCase(); + streamProtos[i].colorSpace = streamStats.getColorSpace(); if (CameraServiceProxy.DEBUG) { String histogramTypeName = @@ -863,7 +864,8 @@ public class CameraServiceProxy extends SystemService + ", histogramCounts " + Arrays.toString(streamProtos[i].histogramCounts) + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile - + ", streamUseCase " + streamProtos[i].streamUseCase); + + ", streamUseCase " + streamProtos[i].streamUseCase + + ", colorSpace " + streamProtos[i].colorSpace); } } } diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index 387e00f73932..2c83c6f419c8 100644 --- a/services/core/java/com/android/server/compat/PlatformCompat.java +++ b/services/core/java/com/android/server/compat/PlatformCompat.java @@ -95,6 +95,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(LOG_COMPAT_CHANGE) public void reportChange(long changeId, ApplicationInfo appInfo) { + super.reportChange_enforcePermission(); + reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); } @@ -102,6 +104,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @EnforcePermission(LOG_COMPAT_CHANGE) public void reportChangeByPackageName(long changeId, String packageName, @UserIdInt int userId) { + super.reportChangeByPackageName_enforcePermission(); + ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo != null) { reportChangeInternal(changeId, appInfo.uid, ChangeReporter.STATE_LOGGED); @@ -111,6 +115,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(LOG_COMPAT_CHANGE) public void reportChangeByUid(long changeId, int uid) { + super.reportChangeByUid_enforcePermission(); + reportChangeInternal(changeId, uid, ChangeReporter.STATE_LOGGED); } @@ -121,6 +127,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) { + super.isChangeEnabled_enforcePermission(); + return isChangeEnabledInternal(changeId, appInfo); } @@ -128,6 +136,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) public boolean isChangeEnabledByPackageName(long changeId, String packageName, @UserIdInt int userId) { + super.isChangeEnabledByPackageName_enforcePermission(); + ApplicationInfo appInfo = getApplicationInfo(packageName, userId); if (appInfo == null) { return mCompatConfig.willChangeBeEnabled(changeId, packageName); @@ -138,6 +148,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) public boolean isChangeEnabledByUid(long changeId, int uid) { + super.isChangeEnabledByUid_enforcePermission(); + String[] packages = mContext.getPackageManager().getPackagesForUid(uid); if (packages == null || packages.length == 0) { return mCompatConfig.defaultChangeIdValue(changeId); @@ -199,6 +211,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public void setOverrides(CompatibilityChangeConfig overrides, String packageName) { + super.setOverrides_enforcePermission(); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); for (long change : overrides.enabledChanges()) { overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); @@ -215,6 +229,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) { + super.setOverridesForTest_enforcePermission(); + Map<Long, PackageOverride> overridesMap = new HashMap<>(); for (long change : overrides.enabledChanges()) { overridesMap.put(change, new PackageOverride.Builder().setEnabled(true).build()); @@ -231,6 +247,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public void putAllOverridesOnReleaseBuilds( CompatibilityOverridesByPackageConfig overridesByPackage) { + super.putAllOverridesOnReleaseBuilds_enforcePermission(); + for (CompatibilityOverrideConfig overrides : overridesByPackage.packageNameToOverrides.values()) { checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); @@ -242,6 +260,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public void putOverridesOnReleaseBuilds(CompatibilityOverrideConfig overrides, String packageName) { + super.putOverridesOnReleaseBuilds_enforcePermission(); + checkAllCompatOverridesAreOverridable(overrides.overrides.keySet()); mCompatConfig.addPackageOverrides(overrides, packageName, /* skipUnknownChangeIds= */ true); } @@ -249,6 +269,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public int enableTargetSdkChanges(String packageName, int targetSdkVersion) { + super.enableTargetSdkChanges_enforcePermission(); + int numChanges = mCompatConfig.enableTargetSdkChangesForPackage(packageName, targetSdkVersion); killPackage(packageName); @@ -258,6 +280,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public int disableTargetSdkChanges(String packageName, int targetSdkVersion) { + super.disableTargetSdkChanges_enforcePermission(); + int numChanges = mCompatConfig.disableTargetSdkChangesForPackage(packageName, targetSdkVersion); killPackage(packageName); @@ -267,6 +291,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public void clearOverrides(String packageName) { + super.clearOverrides_enforcePermission(); + mCompatConfig.removePackageOverrides(packageName); killPackage(packageName); } @@ -274,12 +300,16 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public void clearOverridesForTest(String packageName) { + super.clearOverridesForTest_enforcePermission(); + mCompatConfig.removePackageOverrides(packageName); } @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public boolean clearOverride(long changeId, String packageName) { + super.clearOverride_enforcePermission(); + boolean existed = mCompatConfig.removeOverride(changeId, packageName); killPackage(packageName); return existed; @@ -288,6 +318,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG) public boolean clearOverrideForTest(long changeId, String packageName) { + super.clearOverrideForTest_enforcePermission(); + return mCompatConfig.removeOverride(changeId, packageName); } @@ -295,6 +327,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { @EnforcePermission(OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD) public void removeAllOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveByPackageConfig overridesToRemoveByPackage) { + super.removeAllOverridesOnReleaseBuilds_enforcePermission(); + for (CompatibilityOverridesToRemoveConfig overridesToRemove : overridesToRemoveByPackage.packageNameToOverridesToRemove.values()) { checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); @@ -307,6 +341,8 @@ public class PlatformCompat extends IPlatformCompat.Stub { public void removeOverridesOnReleaseBuilds( CompatibilityOverridesToRemoveConfig overridesToRemove, String packageName) { + super.removeOverridesOnReleaseBuilds_enforcePermission(); + checkAllCompatOverridesAreOverridable(overridesToRemove.changeIds); mCompatConfig.removePackageOverrides(overridesToRemove, packageName); } @@ -314,12 +350,16 @@ public class PlatformCompat extends IPlatformCompat.Stub { @Override @EnforcePermission(allOf = {LOG_COMPAT_CHANGE, READ_COMPAT_CHANGE_CONFIG}) public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) { + super.getAppConfig_enforcePermission(); + return mCompatConfig.getAppConfig(appInfo); } @Override @EnforcePermission(READ_COMPAT_CHANGE_CONFIG) public CompatibilityChangeInfo[] listAllChanges() { + super.listAllChanges_enforcePermission(); + return mCompatConfig.dumpChanges(); } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 45b0f0a6d04a..c671a2c0cdad 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -28,6 +28,7 @@ import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; import static android.os.PowerWhitelistManager.REASON_VPN; import static android.os.UserHandle.PER_USER_RANGE; +import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER; import static java.util.Objects.requireNonNull; @@ -84,6 +85,7 @@ import android.net.VpnManager; import android.net.VpnProfileState; import android.net.VpnService; import android.net.VpnTransportInfo; +import android.net.ipsec.ike.ChildSaProposal; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; import android.net.ipsec.ike.ChildSessionParams; @@ -93,6 +95,7 @@ import android.net.ipsec.ike.IkeSessionConfiguration; import android.net.ipsec.ike.IkeSessionConnectionInfo; import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.IkeTunnelConnectionParams; +import android.net.ipsec.ike.exceptions.IkeIOException; import android.net.ipsec.ike.exceptions.IkeNetworkLostException; import android.net.ipsec.ike.exceptions.IkeNonProtocolException; import android.net.ipsec.ike.exceptions.IkeProtocolException; @@ -140,6 +143,7 @@ import com.android.net.module.util.NetworkStackConstants; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; import com.android.server.net.BaseNetworkObserver; +import com.android.server.vcn.util.MtuUtils; import com.android.server.vcn.util.PersistableBundleUtils; import libcore.io.IoUtils; @@ -152,6 +156,8 @@ import java.io.OutputStream; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; @@ -165,6 +171,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -551,6 +558,24 @@ public class Vpn { return DATA_STALL_RESET_DELAYS_SEC[count]; } } + + /** Gets the MTU of an interface using Java NetworkInterface primitives */ + public int getJavaNetworkInterfaceMtu(@Nullable String iface, int defaultValue) + throws SocketException { + if (iface == null) return defaultValue; + + final NetworkInterface networkInterface = NetworkInterface.getByName(iface); + return networkInterface == null ? defaultValue : networkInterface.getMTU(); + } + + /** Calculates the VPN Network's max MTU based on underlying network and configuration */ + public int calculateVpnMtu( + @NonNull List<ChildSaProposal> childProposals, + int maxMtu, + int underlyingMtu, + boolean isIpv4) { + return MtuUtils.getMtu(childProposals, maxMtu, underlyingMtu, isIpv4); + } } @VisibleForTesting @@ -1397,6 +1422,11 @@ public class Vpn { } private LinkProperties makeLinkProperties() { + // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional + // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU + // is lower than 1280. The logic is controlled by IKEv2VpnRunner, so the design is only + // enabled for IKEv2 VPN. + final boolean disableIPV6 = (isIkev2VpnRunner() && mConfig.mtu < IPV6_MIN_MTU); boolean allowIPv4 = mConfig.allowIPv4; boolean allowIPv6 = mConfig.allowIPv6; @@ -1406,6 +1436,7 @@ public class Vpn { if (mConfig.addresses != null) { for (LinkAddress address : mConfig.addresses) { + if (disableIPV6 && address.isIpv6()) continue; lp.addLinkAddress(address); allowIPv4 |= address.getAddress() instanceof Inet4Address; allowIPv6 |= address.getAddress() instanceof Inet6Address; @@ -1414,8 +1445,9 @@ public class Vpn { if (mConfig.routes != null) { for (RouteInfo route : mConfig.routes) { + final InetAddress address = route.getDestination().getAddress(); + if (disableIPV6 && address instanceof Inet6Address) continue; lp.addRoute(route); - InetAddress address = route.getDestination().getAddress(); if (route.getType() == RouteInfo.RTN_UNICAST) { allowIPv4 |= address instanceof Inet4Address; @@ -1426,7 +1458,8 @@ public class Vpn { if (mConfig.dnsServers != null) { for (String dnsServer : mConfig.dnsServers) { - InetAddress address = InetAddresses.parseNumericAddress(dnsServer); + final InetAddress address = InetAddresses.parseNumericAddress(dnsServer); + if (disableIPV6 && address instanceof Inet6Address) continue; lp.addDnsServer(address); allowIPv4 |= address instanceof Inet4Address; allowIPv6 |= address instanceof Inet6Address; @@ -1440,7 +1473,7 @@ public class Vpn { NetworkStackConstants.IPV4_ADDR_ANY, 0), null /*gateway*/, null /*iface*/, RTN_UNREACHABLE)); } - if (!allowIPv6) { + if (!allowIPv6 || disableIPV6) { lp.addRoute(new RouteInfo(new IpPrefix( NetworkStackConstants.IPV6_ADDR_ANY, 0), null /*gateway*/, null /*iface*/, RTN_UNREACHABLE)); @@ -1576,6 +1609,18 @@ public class Vpn { updateState(DetailedState.DISCONNECTED, "agentDisconnect"); } + @GuardedBy("this") + private void startNewNetworkAgent(NetworkAgent oldNetworkAgent, String reason) { + // Initialize the state for a new agent, while keeping the old one connected + // in case this new connection fails. + mNetworkAgent = null; + updateState(DetailedState.CONNECTING, reason); + // Bringing up a new NetworkAgent to prevent the data leakage before tearing down the old + // NetworkAgent. + agentConnect(); + agentDisconnect(oldNetworkAgent); + } + /** * Establish a VPN network and return the file descriptor of the VPN interface. This methods * returns {@code null} if the application is revoked or not prepared. @@ -1665,16 +1710,7 @@ public class Vpn { setUnderlyingNetworks(config.underlyingNetworks); } } else { - // Initialize the state for a new agent, while keeping the old one connected - // in case this new connection fails. - mNetworkAgent = null; - updateState(DetailedState.CONNECTING, "establish"); - // Set up forwarding and DNS rules. - agentConnect(); - // Remove the old tun's user forwarding rules - // The new tun's user rules have already been added above so they will take over - // as rules are deleted. This prevents data leakage as the rules are moved over. - agentDisconnect(oldNetworkAgent); + startNewNetworkAgent(oldNetworkAgent, "establish"); } if (oldConnection != null) { @@ -2711,6 +2747,17 @@ public class Vpn { void onSessionLost(int token, @Nullable Exception exception); } + private static boolean isIPv6Only(List<LinkAddress> linkAddresses) { + boolean hasIPV6 = false; + boolean hasIPV4 = false; + for (final LinkAddress address : linkAddresses) { + hasIPV6 |= address.isIpv6(); + hasIPV4 |= address.isIpv4(); + } + + return hasIPV6 && !hasIPV4; + } + /** * Internal class managing IKEv2/IPsec VPN connectivity * @@ -2924,15 +2971,27 @@ public class Vpn { try { final String interfaceName = mTunnelIface.getInterfaceName(); - final int maxMtu = mProfile.getMaxMtu(); final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses(); final List<String> dnsAddrStrings = new ArrayList<>(); + int vpnMtu; + vpnMtu = calculateVpnMtu(); + + // If the VPN is IPv6 only and its MTU is lower than 1280, mark the network as lost + // and send the VpnManager event to the VPN app. + if (isIPv6Only(internalAddresses) && vpnMtu < IPV6_MIN_MTU) { + onSessionLost( + token, + new IkeIOException( + new IOException("No valid addresses for MTU < 1280"))); + return; + } final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors( childConfig.getOutboundTrafficSelectors()); for (final LinkAddress address : internalAddresses) { mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength()); } + for (InetAddress addr : childConfig.getInternalDnsServers()) { dnsAddrStrings.add(addr.getHostAddress()); } @@ -2950,7 +3009,7 @@ public class Vpn { if (mVpnRunner != this) return; mInterface = interfaceName; - mConfig.mtu = maxMtu; + mConfig.mtu = vpnMtu; mConfig.interfaze = mInterface; mConfig.addresses.clear(); @@ -3053,12 +3112,54 @@ public class Vpn { // Ignore stale runner. if (mVpnRunner != this) return; + final LinkProperties oldLp = makeLinkProperties(); + + final boolean underlyingNetworkHasChanged = + !Arrays.equals(mConfig.underlyingNetworks, new Network[]{network}); mConfig.underlyingNetworks = new Network[] {network}; - mNetworkCapabilities = - new NetworkCapabilities.Builder(mNetworkCapabilities) - .setUnderlyingNetworks(Collections.singletonList(network)) - .build(); - doSetUnderlyingNetworks(mNetworkAgent, Collections.singletonList(network)); + mConfig.mtu = calculateVpnMtu(); + + final LinkProperties newLp = makeLinkProperties(); + + // If MTU is < 1280, IPv6 addresses will be removed. If there are no addresses + // left (e.g. IPv6-only VPN network), mark VPN as having lost the session. + if (newLp.getLinkAddresses().isEmpty()) { + onSessionLost( + token, + new IkeIOException( + new IOException("No valid addresses for MTU < 1280"))); + return; + } + + final Set<LinkAddress> removedAddrs = new HashSet<>(oldLp.getLinkAddresses()); + removedAddrs.removeAll(newLp.getLinkAddresses()); + + // If addresses were removed despite no IKE config change, IPv6 addresses must + // have been removed due to MTU size. Restart the VPN to ensure all IPv6 + // unconnected sockets on the new VPN network are closed and retried on the new + // VPN network. + if (!removedAddrs.isEmpty()) { + startNewNetworkAgent( + mNetworkAgent, "MTU too low for IPv6; restarting network agent"); + + for (LinkAddress removed : removedAddrs) { + mTunnelIface.removeAddress( + removed.getAddress(), removed.getPrefixLength()); + } + } else { + // Put below 3 updates into else block is because agentConnect() will do + // those things, so there is no need to do the redundant work. + if (!newLp.equals(oldLp)) doSendLinkProperties(mNetworkAgent, newLp); + if (underlyingNetworkHasChanged) { + mNetworkCapabilities = + new NetworkCapabilities.Builder(mNetworkCapabilities) + .setUnderlyingNetworks( + Collections.singletonList(network)) + .build(); + doSetUnderlyingNetworks(mNetworkAgent, + Collections.singletonList(network)); + } + } } mTunnelIface.setUnderlyingNetwork(network); @@ -3108,6 +3209,60 @@ public class Vpn { startOrMigrateIkeSession(network); } + @NonNull + private IkeSessionParams getIkeSessionParams(@NonNull Network underlyingNetwork) { + final IkeTunnelConnectionParams ikeTunConnParams = + mProfile.getIkeTunnelConnectionParams(); + if (ikeTunConnParams != null) { + final IkeSessionParams.Builder builder = + new IkeSessionParams.Builder(ikeTunConnParams.getIkeSessionParams()) + .setNetwork(underlyingNetwork); + return builder.build(); + } else { + return VpnIkev2Utils.buildIkeSessionParams(mContext, mProfile, underlyingNetwork); + } + } + + @NonNull + private ChildSessionParams getChildSessionParams() { + final IkeTunnelConnectionParams ikeTunConnParams = + mProfile.getIkeTunnelConnectionParams(); + if (ikeTunConnParams != null) { + return ikeTunConnParams.getTunnelModeChildSessionParams(); + } else { + return VpnIkev2Utils.buildChildSessionParams(mProfile.getAllowedAlgorithms()); + } + } + + private int calculateVpnMtu() { + final Network underlyingNetwork = mIkeConnectionInfo.getNetwork(); + final LinkProperties lp = mConnectivityManager.getLinkProperties(underlyingNetwork); + if (underlyingNetwork == null || lp == null) { + // Return the max MTU defined in VpnProfile as the fallback option when there is no + // underlying network or LinkProperties is null. + return mProfile.getMaxMtu(); + } + + int underlyingMtu = lp.getMtu(); + + // Try to get MTU from kernel if MTU is not set in LinkProperties. + if (underlyingMtu == 0) { + try { + underlyingMtu = mDeps.getJavaNetworkInterfaceMtu(lp.getInterfaceName(), + mProfile.getMaxMtu()); + } catch (SocketException e) { + Log.d(TAG, "Got a SocketException when getting MTU from kernel: " + e); + return mProfile.getMaxMtu(); + } + } + + return mDeps.calculateVpnMtu( + getChildSessionParams().getSaProposals(), + mProfile.getMaxMtu(), + underlyingMtu, + mIkeConnectionInfo.getLocalAddress() instanceof Inet4Address); + } + /** * Start a new IKE session. * @@ -3158,24 +3313,6 @@ public class Vpn { // (non-default) network, and start the new one. resetIkeState(); - // Get Ike options from IkeTunnelConnectionParams if it's available in the - // profile. - final IkeTunnelConnectionParams ikeTunConnParams = - mProfile.getIkeTunnelConnectionParams(); - final IkeSessionParams ikeSessionParams; - final ChildSessionParams childSessionParams; - if (ikeTunConnParams != null) { - final IkeSessionParams.Builder builder = new IkeSessionParams.Builder( - ikeTunConnParams.getIkeSessionParams()).setNetwork(underlyingNetwork); - ikeSessionParams = builder.build(); - childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams(); - } else { - ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams( - mContext, mProfile, underlyingNetwork); - childSessionParams = VpnIkev2Utils.buildChildSessionParams( - mProfile.getAllowedAlgorithms()); - } - // TODO: Remove the need for adding two unused addresses with // IPsec tunnels. final InetAddress address = InetAddress.getLocalHost(); @@ -3193,8 +3330,8 @@ public class Vpn { mSession = mIkev2SessionCreator.createIkeSession( mContext, - ikeSessionParams, - childSessionParams, + getIkeSessionParams(underlyingNetwork), + getChildSessionParams(), mExecutor, new VpnIkev2Utils.IkeSessionCallbackImpl( TAG, IkeV2VpnRunner.this, token), diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a3b1a420b180..523a2dca7a18 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -699,7 +699,6 @@ public class DisplayDeviceConfig { */ public float getNitsFromBacklight(float backlight) { if (mBacklightToNitsSpline == null) { - Slog.wtf(TAG, "requesting nits when no mapping exists."); return NITS_INVALID; } backlight = Math.max(backlight, mBacklightMinimum); diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java index aa9f2dc2d7cb..726326f33b92 100644 --- a/services/core/java/com/android/server/display/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/DisplayModeDirector.java @@ -33,7 +33,7 @@ import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.net.Uri; import android.os.Handler; import android.os.IThermalEventListener; @@ -2359,39 +2359,39 @@ public class DisplayModeDirector { } } - private class UdfpsObserver extends IUdfpsHbmListener.Stub { - private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray(); + private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub { + private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray(); public void observe() { StatusBarManagerInternal statusBar = LocalServices.getService(StatusBarManagerInternal.class); if (statusBar != null) { - statusBar.setUdfpsHbmListener(this); + statusBar.setUdfpsRefreshRateCallback(this); } } @Override - public void onHbmEnabled(int displayId) { + public void onRequestEnabled(int displayId) { synchronized (mLock) { - updateHbmStateLocked(displayId, true /*enabled*/); + updateRefreshRateStateLocked(displayId, true /*enabled*/); } } @Override - public void onHbmDisabled(int displayId) { + public void onRequestDisabled(int displayId) { synchronized (mLock) { - updateHbmStateLocked(displayId, false /*enabled*/); + updateRefreshRateStateLocked(displayId, false /*enabled*/); } } - private void updateHbmStateLocked(int displayId, boolean enabled) { - mLocalHbmEnabled.put(displayId, enabled); + private void updateRefreshRateStateLocked(int displayId, boolean enabled) { + mUdfpsRefreshRateEnabled.put(displayId, enabled); updateVoteLocked(displayId); } private void updateVoteLocked(int displayId) { final Vote vote; - if (mLocalHbmEnabled.get(displayId)) { + if (mUdfpsRefreshRateEnabled.get(displayId)) { Display.Mode[] modes = mSupportedModesByDisplay.get(displayId); float maxRefreshRate = 0f; for (Display.Mode mode : modes) { @@ -2409,10 +2409,10 @@ public class DisplayModeDirector { void dumpLocked(PrintWriter pw) { pw.println(" UdfpsObserver"); - pw.println(" mLocalHbmEnabled: "); - for (int i = 0; i < mLocalHbmEnabled.size(); i++) { - final int displayId = mLocalHbmEnabled.keyAt(i); - final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled"; + pw.println(" mUdfpsRefreshRateEnabled: "); + for (int i = 0; i < mUdfpsRefreshRateEnabled.size(); i++) { + final int displayId = mUdfpsRefreshRateEnabled.keyAt(i); + final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled"; pw.println(" Display " + displayId + ": " + enabled); } } diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 21a851895c15..58248874d359 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -1682,6 +1682,8 @@ public final class ColorDisplayService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) @Override public boolean isSaturationActivated() { + super.isSaturationActivated_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { return !mGlobalSaturationTintController.isActivatedStateNotSet() @@ -1694,6 +1696,8 @@ public final class ColorDisplayService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) @Override public boolean setAppSaturationLevel(String packageName, int level) { + super.setAppSaturationLevel_enforcePermission(); + final String callingPackageName = LocalServices.getService(PackageManagerInternal.class) .getNameForUid(Binder.getCallingUid()); final long token = Binder.clearCallingIdentity(); @@ -1706,6 +1710,8 @@ public final class ColorDisplayService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) public int getTransformCapabilities() { + super.getTransformCapabilities_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { return getTransformCapabilitiesInternal(); diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 326d720927ab..2817d1bd5097 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -68,6 +68,8 @@ public final class FontManagerService extends IFontManager.Stub { @RequiresPermission(Manifest.permission.UPDATE_FONTS) @Override public FontConfig getFontConfig() { + super.getFontConfig_enforcePermission(); + return getSystemFontConfig(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 3be145021332..fcdb1f58f458 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -144,6 +144,7 @@ import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.DirectBootAwareness; @@ -1592,8 +1593,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private final InputMethodManagerService mService; public Lifecycle(Context context) { + this(context, new InputMethodManagerService(context)); + } + + public Lifecycle( + Context context, @NonNull InputMethodManagerService inputMethodManagerService) { super(context); - mService = new InputMethodManagerService(context); + mService = inputMethodManagerService; } @Override @@ -1668,12 +1674,25 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } public InputMethodManagerService(Context context) { + this(context, null, null); + } + + @VisibleForTesting + InputMethodManagerService( + Context context, + @Nullable ServiceThread serviceThreadForTesting, + @Nullable InputMethodBindingController bindingControllerForTesting) { mContext = context; mRes = context.getResources(); // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading // additional subtypes in switchUserOnHandlerLocked(). - final ServiceThread thread = new ServiceThread( - HANDLER_THREAD_NAME, Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); + final ServiceThread thread = + serviceThreadForTesting != null + ? serviceThreadForTesting + : new ServiceThread( + HANDLER_THREAD_NAME, + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); thread.start(); mHandler = Handler.createAsync(thread.getLooper(), this); // Note: SettingsObserver doesn't register observers in its constructor. @@ -1701,10 +1720,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateCurrentProfileIds(); AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); - mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mSettings, context); + mSwitchingController = + InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context); mMenuController = new InputMethodMenuController(this); - mBindingController = new InputMethodBindingController(this); + mBindingController = + bindingControllerForTesting != null + ? bindingControllerForTesting + : new InputMethodBindingController(this); mAutofillController = new AutofillSuggestionsController(this); mPreventImeStartupUnlessTextEditor = mRes.getBoolean( com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); @@ -3673,6 +3695,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // UI for input. if (isTextEditor && editorInfo != null && shouldRestoreImeVisibility(windowToken, softInputMode)) { + if (DEBUG) Slog.v(TAG, "Will show input to restore visibility"); res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); @@ -3719,11 +3742,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub imeDispatcher); didStart = true; } - showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + showCurrentInputLocked( + windowToken, + InputMethodManager.SHOW_IMPLICIT, + null, SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); } break; case LayoutParams.SOFT_INPUT_STATE_UNCHANGED: + if (DEBUG) { + Slog.v(TAG, "Window asks to keep the input in whatever state it was last in"); + } // Do nothing. break; case LayoutParams.SOFT_INPUT_STATE_HIDDEN: @@ -3794,6 +3823,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // To maintain compatibility, we are now hiding the IME when we don't have // an editor upon refocusing a window. if (startInputByWinGainedFocus) { + if (DEBUG) Slog.v(TAG, "Same window without editor will hide input"); hideCurrentInputLocked(mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); } @@ -3807,6 +3837,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor // 2) SOFT_INPUT_STATE_VISIBLE state without an editor // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor + if (DEBUG) Slog.v(TAG, "Window without editor will hide input"); hideCurrentInputLocked(mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR); } @@ -3897,6 +3928,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) { // Always call subtype picker, because subtype picker is a superset of input method // picker. + super.showInputMethodPickerFromSystem_enforcePermission(); + mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId) .sendToTarget(); } @@ -3906,6 +3939,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShownForTest() { + super.isInputMethodPickerShownForTest_enforcePermission(); + synchronized (ImfLock.class) { return mMenuController.isisInputMethodPickerShownForTestLocked(); } @@ -4185,6 +4220,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW) @Override public void removeImeSurface() { + super.removeImeSurface_enforcePermission(); + mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget(); } @@ -4384,6 +4421,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD) @Override public void addVirtualStylusIdForTestSession(IInputMethodClient client) { + super.addVirtualStylusIdForTestSession_enforcePermission(); + int uid = Binder.getCallingUid(); synchronized (ImfLock.class) { if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) { @@ -4409,6 +4448,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void setStylusWindowIdleTimeoutForTest( IInputMethodClient client, @DurationMillisLong long timeout) { + super.setStylusWindowIdleTimeoutForTest_enforcePermission(); + int uid = Binder.getCallingUid(); synchronized (ImfLock.class) { if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) { @@ -4506,6 +4547,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING) @Override public void startImeTrace() { + super.startImeTrace_enforcePermission(); + ImeTracing.getInstance().startTrace(null /* printwriter */); ArrayMap<IBinder, ClientState> clients; synchronized (ImfLock.class) { @@ -4522,6 +4565,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING) @Override public void stopImeTrace() { + super.stopImeTrace_enforcePermission(); + ImeTracing.getInstance().stopTrace(null /* printwriter */); ArrayMap<IBinder, ClientState> clients; synchronized (ImfLock.class) { diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 9bd48f2e0a58..dcec0aaf9358 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -950,6 +950,8 @@ public class LocationManagerService extends ILocationManager.Stub implements @Override public void injectLocation(Location location) { + super.injectLocation_enforcePermission(); + Preconditions.checkArgument(location.isComplete()); int userId = UserHandle.getCallingUserId(); @@ -1160,6 +1162,8 @@ public class LocationManagerService extends ILocationManager.Stub implements @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public void setExtraLocationControllerPackage(String packageName) { + super.setExtraLocationControllerPackage_enforcePermission(); + synchronized (mLock) { mExtraLocationControllerPackage = packageName; } @@ -1175,6 +1179,8 @@ public class LocationManagerService extends ILocationManager.Stub implements @android.annotation.EnforcePermission(android.Manifest.permission.LOCATION_HARDWARE) @Override public void setExtraLocationControllerPackageEnabled(boolean enabled) { + super.setExtraLocationControllerPackageEnabled_enforcePermission(); + synchronized (mLock) { mExtraLocationControllerPackageEnabled = enabled; } @@ -1234,6 +1240,8 @@ public class LocationManagerService extends ILocationManager.Stub implements @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public void setAutomotiveGnssSuspended(boolean suspended) { + super.setAutomotiveGnssSuspended_enforcePermission(); + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { throw new IllegalStateException( "setAutomotiveGnssSuspended only allowed on automotive devices"); @@ -1247,6 +1255,8 @@ public class LocationManagerService extends ILocationManager.Stub implements @RequiresPermission(android.Manifest.permission.CONTROL_AUTOMOTIVE_GNSS) public boolean isAutomotiveGnssSuspended() { + super.isAutomotiveGnssSuspended_enforcePermission(); + if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { throw new IllegalStateException( "isAutomotiveGnssSuspended only allowed on automotive devices"); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 51851bedba73..90245b5ebda9 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -129,7 +129,7 @@ public class ContextHubService extends IContextHubService.Stub { new RemoteCallbackList<>(); // Proxy object to communicate with the Context Hub HAL - private IContextHubWrapper mContextHubWrapper; + private final IContextHubWrapper mContextHubWrapper; // The manager for transaction queue private ContextHubTransactionManager mTransactionManager; @@ -207,11 +207,35 @@ public class ContextHubService extends IContextHubService.Stub { handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions, messagePermissions); } + + @Override + public void handleServiceRestart() { + Log.i(TAG, "Starting Context Hub Service restart"); + initExistingCallbacks(); + resetSettings(); + Log.i(TAG, "Finished Context Hub Service restart"); + } } public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) { + Log.i(TAG, "Starting Context Hub Service init"); mContext = context; - init(contextHubWrapper, /* isFirstInit= */ true); + long startTimeNs = SystemClock.elapsedRealtimeNanos(); + mContextHubWrapper = contextHubWrapper; + if (!initContextHubServiceState(startTimeNs)) { + Log.e(TAG, "Failed to initialize the Context Hub Service"); + return; + } + initDefaultClientMap(); + + initLocationSettingNotifications(); + initWifiSettingNotifications(); + initAirplaneModeSettingNotifications(); + initMicrophoneSettingNotifications(); + initBtSettingNotifications(); + + scheduleDailyMetricSnapshot(); + Log.i(TAG, "Finished Context Hub Service init"); } /** @@ -293,11 +317,10 @@ public class ContextHubService extends IContextHubService.Stub { * Initializes the private state of the ContextHubService * * @param startTimeNs the start time when init was called - * @param isFirstInit if true, this is the first time init is called - boot time * * @return if mContextHubWrapper is not null and a full state init was done */ - private boolean initContextHubServiceState(long startTimeNs, boolean isFirstInit) { + private boolean initContextHubServiceState(long startTimeNs) { if (mContextHubWrapper == null) { mTransactionManager = null; mClientManager = null; @@ -317,12 +340,10 @@ public class ContextHubService extends IContextHubService.Stub { hubInfo = new Pair(Collections.emptyList(), Collections.emptyList()); } - if (isFirstInit) { - long bootTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs; - int numContextHubs = hubInfo.first.size(); - ContextHubStatsLog.write(ContextHubStatsLog.CONTEXT_HUB_BOOTED, bootTimeNs, - numContextHubs); - } + long bootTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs; + int numContextHubs = hubInfo.first.size(); + ContextHubStatsLog.write(ContextHubStatsLog.CONTEXT_HUB_BOOTED, bootTimeNs, + numContextHubs); mContextHubIdToInfoMap = Collections.unmodifiableMap( ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first)); @@ -368,6 +389,20 @@ public class ContextHubService extends IContextHubService.Stub { } /** + * Initializes existing callbacks with the mContextHubWrapper for every context hub + */ + private void initExistingCallbacks() { + for (int contextHubId : mContextHubIdToInfoMap.keySet()) { + try { + mContextHubWrapper.registerExistingCallback(contextHubId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while registering existing service callback for hub " + + "(ID = " + contextHubId + ")", e); + } + } + } + + /** * Handles the initialization of location settings notifications */ private void initLocationSettingNotifications() { @@ -495,6 +530,17 @@ public class ContextHubService extends IContextHubService.Stub { mContext.registerReceiver(btReceiver, filter); } + /** + * Resets the settings. Called when a context hub restarts or the AIDL HAL dies + */ + private void resetSettings() { + sendLocationSettingUpdate(); + sendWifiSettingUpdate(/* forceUpdate= */ true); + sendAirplaneModeSettingUpdate(); + sendMicrophoneDisableSettingUpdateForCurrentUser(); + sendBtSettingUpdate(/* forceUpdate= */ true); + } + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver result) { @@ -504,6 +550,8 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public int registerCallback(IContextHubCallback callback) throws RemoteException { + super.registerCallback_enforcePermission(); + mCallbacksList.register(callback); Log.d(TAG, "Added callback, total callbacks " + @@ -514,12 +562,16 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public int[] getContextHubHandles() throws RemoteException { + super.getContextHubHandles_enforcePermission(); + return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet()); } @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException { + super.getContextHubInfo_enforcePermission(); + if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) { Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo"); return null; @@ -536,6 +588,8 @@ public class ContextHubService extends IContextHubService.Stub { */ @Override public List<ContextHubInfo> getContextHubs() throws RemoteException { + super.getContextHubs_enforcePermission(); + return mContextHubInfoList; } @@ -602,6 +656,8 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException { + super.loadNanoApp_enforcePermission(); + if (mContextHubWrapper == null) { return -1; } @@ -629,6 +685,8 @@ public class ContextHubService extends IContextHubService.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Override public int unloadNanoApp(int nanoAppHandle) throws RemoteException { + super.unloadNanoApp_enforcePermission(); + if (mContextHubWrapper == null) { return -1; } @@ -655,6 +713,8 @@ public class ContextHubService extends IContextHubService.Stub { @Override public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) throws RemoteException { + super.getNanoAppInstanceInfo_enforcePermission(); + return mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle); } @@ -663,6 +723,8 @@ public class ContextHubService extends IContextHubService.Stub { public int[] findNanoAppOnHub( int contextHubHandle, NanoAppFilter filter) throws RemoteException { + super.findNanoAppOnHub_enforcePermission(); + ArrayList<Integer> foundInstances = new ArrayList<>(); if (filter != null) { mNanoAppStateManager.foreachNanoAppInstanceInfo((info) -> { @@ -707,6 +769,8 @@ public class ContextHubService extends IContextHubService.Stub { @Override public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException { + super.sendMessage_enforcePermission(); + if (mContextHubWrapper == null) { return -1; } @@ -749,31 +813,6 @@ public class ContextHubService extends IContextHubService.Stub { } /** - * Handles a service restart or service init for the first time - * - * @param contextHubWrapper the Context Hub wrapper - * @param isFirstInit if true, this is the first time init is called - boot time - */ - private void init(IContextHubWrapper contextHubWrapper, boolean isFirstInit) { - Log.i(TAG, "Starting Context Hub Service init"); - long startTimeNs = SystemClock.elapsedRealtimeNanos(); - mContextHubWrapper = contextHubWrapper; - if (!initContextHubServiceState(startTimeNs, isFirstInit)) { - Log.e(TAG, "Failed to initialize the Context Hub Service"); - return; - } - initDefaultClientMap(); - - initLocationSettingNotifications(); - initWifiSettingNotifications(); - initAirplaneModeSettingNotifications(); - initMicrophoneSettingNotifications(); - initBtSettingNotifications(); - - scheduleDailyMetricSnapshot(); - } - - /** * Handles a unicast or broadcast message from a nanoapp. * * @param contextHubId the ID of the hub the message came from @@ -853,11 +892,7 @@ public class ContextHubService extends IContextHubService.Stub { ContextHubEventLogger.getInstance().logContextHubRestart(contextHubId); - sendLocationSettingUpdate(); - sendWifiSettingUpdate(/* forceUpdate= */ true); - sendAirplaneModeSettingUpdate(); - sendMicrophoneDisableSettingUpdateForCurrentUser(); - sendBtSettingUpdate(/* forceUpdate= */ true); + resetSettings(); mTransactionManager.onHubReset(); queryNanoAppsInternal(contextHubId); @@ -928,6 +963,8 @@ public class ContextHubService extends IContextHubService.Stub { public IContextHubClient createClient( int contextHubId, IContextHubClientCallback clientCallback, @Nullable String attributionTag, String packageName) throws RemoteException { + super.createClient_enforcePermission(); + if (!isValidContextHubId(contextHubId)) { throw new IllegalArgumentException("Invalid context hub ID " + contextHubId); } @@ -956,6 +993,8 @@ public class ContextHubService extends IContextHubService.Stub { public IContextHubClient createPendingIntentClient( int contextHubId, PendingIntent pendingIntent, long nanoAppId, @Nullable String attributionTag) throws RemoteException { + super.createPendingIntentClient_enforcePermission(); + if (!isValidContextHubId(contextHubId)) { throw new IllegalArgumentException("Invalid context hub ID " + contextHubId); } @@ -978,6 +1017,8 @@ public class ContextHubService extends IContextHubService.Stub { public void loadNanoAppOnHub( int contextHubId, IContextHubTransactionCallback transactionCallback, NanoAppBinary nanoAppBinary) throws RemoteException { + super.loadNanoAppOnHub_enforcePermission(); + if (!checkHalProxyAndContextHubId( contextHubId, transactionCallback, ContextHubTransaction.TYPE_LOAD_NANOAPP)) { return; @@ -1007,6 +1048,8 @@ public class ContextHubService extends IContextHubService.Stub { public void unloadNanoAppFromHub( int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) throws RemoteException { + super.unloadNanoAppFromHub_enforcePermission(); + if (!checkHalProxyAndContextHubId( contextHubId, transactionCallback, ContextHubTransaction.TYPE_UNLOAD_NANOAPP)) { return; @@ -1030,6 +1073,8 @@ public class ContextHubService extends IContextHubService.Stub { public void enableNanoApp( int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) throws RemoteException { + super.enableNanoApp_enforcePermission(); + if (!checkHalProxyAndContextHubId( contextHubId, transactionCallback, ContextHubTransaction.TYPE_ENABLE_NANOAPP)) { return; @@ -1053,6 +1098,8 @@ public class ContextHubService extends IContextHubService.Stub { public void disableNanoApp( int contextHubId, IContextHubTransactionCallback transactionCallback, long nanoAppId) throws RemoteException { + super.disableNanoApp_enforcePermission(); + if (!checkHalProxyAndContextHubId( contextHubId, transactionCallback, ContextHubTransaction.TYPE_DISABLE_NANOAPP)) { return; @@ -1074,6 +1121,8 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void queryNanoApps(int contextHubId, IContextHubTransactionCallback transactionCallback) throws RemoteException { + super.queryNanoApps_enforcePermission(); + if (!checkHalProxyAndContextHubId( contextHubId, transactionCallback, ContextHubTransaction.TYPE_QUERY_NANOAPPS)) { return; diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 432b097afe83..48152b40aaf6 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -32,6 +32,7 @@ import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -92,6 +93,11 @@ public abstract class IContextHubWrapper { */ void handleNanoappMessage(short hostEndpointId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions); + + /** + * Handles a restart of the service + */ + void handleServiceRestart(); } /** @@ -170,12 +176,9 @@ public abstract class IContextHubWrapper { } /** - * Attempts to connect to the Contexthub HAL AIDL service, if it exists. - * - * @return A valid IContextHubWrapper if the connection was successful, null otherwise. + * Attempts to connect to the AIDL HAL and returns the proxy IContextHub. */ - @Nullable - public static IContextHubWrapper maybeConnectToAidl() { + public static android.hardware.contexthub.IContextHub maybeConnectToAidlGetProxy() { android.hardware.contexthub.IContextHub proxy = null; final String aidlServiceName = android.hardware.contexthub.IContextHub.class.getCanonicalName() + "/default"; @@ -188,8 +191,18 @@ public abstract class IContextHubWrapper { } else { Log.d(TAG, "Context Hub AIDL service is not declared"); } + return proxy; + } - return (proxy == null) ? null : new ContextHubWrapperAidl(proxy); + /** + * Attempts to connect to the Contexthub HAL AIDL service, if it exists. + * + * @return A valid IContextHubWrapper if the connection was successful, null otherwise. + */ + @Nullable + public static IContextHubWrapper maybeConnectToAidl() { + android.hardware.contexthub.IContextHub proxy = maybeConnectToAidlGetProxy(); + return proxy == null ? null : new ContextHubWrapperAidl(proxy); } /** @@ -354,12 +367,22 @@ public abstract class IContextHubWrapper { public abstract void registerCallback(int contextHubId, @NonNull ICallback callback) throws RemoteException; - private static class ContextHubWrapperAidl extends IContextHubWrapper { + /** + * Registers an existing callback with the Context Hub. + * + * @param contextHubId The ID of the Context Hub to register the callback with. + */ + public abstract void registerExistingCallback(int contextHubId) throws RemoteException; + + private static class ContextHubWrapperAidl extends IContextHubWrapper + implements IBinder.DeathRecipient { private android.hardware.contexthub.IContextHub mHub; private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap = new HashMap<>(); + private Runnable mHandleServiceRestartCallback = null; + // Use this thread in case where the execution requires to be on a service thread. // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission. private HandlerThread mHandlerThread = @@ -419,17 +442,51 @@ public abstract class IContextHubWrapper { } ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub) { - mHub = hub; + setHub(hub); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + linkWrapperToHubDeath(); + } + + private synchronized android.hardware.contexthub.IContextHub getHub() { + return mHub; + } + + private synchronized void setHub(android.hardware.contexthub.IContextHub hub) { + mHub = hub; + } + + @Override + public void binderDied() { + Log.i(TAG, "Context Hub AIDL HAL died"); + + setHub(maybeConnectToAidlGetProxy()); + if (getHub() == null) { + // TODO(b/256860015): Make this reconnection more robust + Log.e(TAG, "Could not reconnect to Context Hub AIDL HAL"); + return; + } + linkWrapperToHubDeath(); + + if (mHandleServiceRestartCallback != null) { + mHandleServiceRestartCallback.run(); + } else { + Log.e(TAG, "mHandleServiceRestartCallback is not set"); + } } public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(), + new ArrayList<String>()); + } + Set<String> supportedPermissions = new HashSet<>(); ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>(); - for (android.hardware.contexthub.ContextHubInfo hub : mHub.getContextHubs()) { - hubInfoList.add(new ContextHubInfo(hub)); - for (String permission : hub.supportedPermissions) { + for (android.hardware.contexthub.ContextHubInfo hubInfo : hub.getContextHubs()) { + hubInfoList.add(new ContextHubInfo(hubInfo)); + for (String permission : hubInfo.supportedPermissions) { supportedPermissions.add(permission); } } @@ -489,8 +546,13 @@ public abstract class IContextHubWrapper { @Override public void onHostEndpointConnected(HostEndpointInfo info) { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + try { - mHub.onHostEndpointConnected(info); + hub.onHostEndpointConnected(info); } catch (RemoteException | ServiceSpecificException e) { Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage()); } @@ -498,8 +560,13 @@ public abstract class IContextHubWrapper { @Override public void onHostEndpointDisconnected(short hostEndpointId) { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + try { - mHub.onHostEndpointDisconnected((char) hostEndpointId); + hub.onHostEndpointDisconnected((char) hostEndpointId); } catch (RemoteException | ServiceSpecificException e) { Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage()); } @@ -509,8 +576,13 @@ public abstract class IContextHubWrapper { public int sendMessageToContextHub( short hostEndpointId, int contextHubId, NanoAppMessage message) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + try { - mHub.sendMessageToHub(contextHubId, + hub.sendMessageToHub(contextHubId, ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message)); return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException e) { @@ -523,10 +595,15 @@ public abstract class IContextHubWrapper { @ContextHubTransaction.Result public int loadNanoapp(int contextHubId, NanoAppBinary binary, int transactionId) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + android.hardware.contexthub.NanoappBinary aidlNanoAppBinary = ContextHubServiceUtil.createAidlNanoAppBinary(binary); try { - mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId); + hub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId); return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; @@ -538,8 +615,13 @@ public abstract class IContextHubWrapper { @ContextHubTransaction.Result public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + try { - mHub.unloadNanoapp(contextHubId, nanoappId, transactionId); + hub.unloadNanoapp(contextHubId, nanoappId, transactionId); return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; @@ -551,8 +633,13 @@ public abstract class IContextHubWrapper { @ContextHubTransaction.Result public int enableNanoapp(int contextHubId, long nanoappId, int transactionId) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + try { - mHub.enableNanoapp(contextHubId, nanoappId, transactionId); + hub.enableNanoapp(contextHubId, nanoappId, transactionId); return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; @@ -564,8 +651,13 @@ public abstract class IContextHubWrapper { @ContextHubTransaction.Result public int disableNanoapp(int contextHubId, long nanoappId, int transactionId) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + try { - mHub.disableNanoapp(contextHubId, nanoappId, transactionId); + hub.disableNanoapp(contextHubId, nanoappId, transactionId); return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; @@ -576,8 +668,13 @@ public abstract class IContextHubWrapper { @ContextHubTransaction.Result public int queryNanoapps(int contextHubId) throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; + } + try { - mHub.queryNanoapps(contextHubId); + hub.queryNanoapps(contextHubId); return ContextHubTransaction.RESULT_SUCCESS; } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) { return ContextHubTransaction.RESULT_FAILED_UNKNOWN; @@ -586,22 +683,65 @@ public abstract class IContextHubWrapper { } } - public void registerCallback(int contextHubId, ICallback callback) throws RemoteException { - mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback)); + public void registerExistingCallback(int contextHubId) { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + + ContextHubAidlCallback callback = mAidlCallbackMap.get(contextHubId); + if (callback == null) { + Log.e(TAG, "Could not find existing callback to register for context hub ID = " + + contextHubId); + return; + } + try { - mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId)); + hub.registerCallback(contextHubId, callback); } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) { Log.e(TAG, "Exception while registering callback: " + e.getMessage()); } } + public void registerCallback(int contextHubId, ICallback callback) { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + + mHandleServiceRestartCallback = callback::handleServiceRestart; + mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback)); + registerExistingCallback(contextHubId); + } + private void onSettingChanged(byte setting, boolean enabled) { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + try { - mHub.onSettingChanged(setting, enabled); + hub.onSettingChanged(setting, enabled); } catch (RemoteException | ServiceSpecificException e) { Log.e(TAG, "Exception while sending setting update: " + e.getMessage()); } } + + /** + * Links the mHub death handler to this + */ + private void linkWrapperToHubDeath() { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return; + } + + try { + hub.asBinder().linkToDeath(this, 0); + } catch (RemoteException exception) { + Log.e(TAG, "Context Hub AIDL service death receipt could not be linked"); + } + } } /** @@ -729,6 +869,17 @@ public abstract class IContextHubWrapper { mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId)); } + public void registerExistingCallback(int contextHubId) throws RemoteException { + ContextHubWrapperHidlCallback callback = mHidlCallbackMap.get(contextHubId); + if (callback == null) { + Log.e(TAG, "Could not find existing callback for context hub with ID = " + + contextHubId); + return; + } + + mHub.registerCallback(contextHubId, callback); + } + public boolean supportsBtSettingNotifications() { return false; } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 439e9bd2624e..f5ec880ed235 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -131,8 +131,6 @@ class MediaRouter2ServiceImpl { UserHandler::updateDiscoveryPreferenceOnHandler, userHandler)); } } - - mEventLogger.enqueue(new EventLogger.StringEvent("mScreenOnOffReceiver", null)); } }; @@ -151,7 +149,7 @@ class MediaRouter2ServiceImpl { mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter); } - // Methods that implement MediaRouter2 operations. + // Start of methods that implement MediaRouter2 operations. @NonNull public void enforceMediaContentControlPermission() { @@ -199,46 +197,6 @@ class MediaRouter2ServiceImpl { } } - @NonNull - public RoutingSessionInfo getSystemSessionInfo( - @Nullable String packageName, boolean setDeviceRouteSelected) { - final int uid = Binder.getCallingUid(); - final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); - final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) - == PackageManager.PERMISSION_GRANTED; - - final long token = Binder.clearCallingIdentity(); - try { - RoutingSessionInfo systemSessionInfo = null; - synchronized (mLock) { - UserRecord userRecord = getOrCreateUserRecordLocked(userId); - List<RoutingSessionInfo> sessionInfos; - if (hasModifyAudioRoutingPermission) { - if (setDeviceRouteSelected) { - systemSessionInfo = userRecord.mHandler.mSystemProvider - .generateDeviceRouteSelectedSessionInfo(packageName); - } else { - sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos(); - if (sessionInfos != null && !sessionInfos.isEmpty()) { - systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0)) - .setClientPackageName(packageName).build(); - } else { - Slog.w(TAG, "System provider does not have any session info."); - } - } - } else { - systemSessionInfo = new RoutingSessionInfo.Builder( - userRecord.mHandler.mSystemProvider.getDefaultSessionInfo()) - .setClientPackageName(packageName).build(); - } - } - return systemSessionInfo; - } finally { - Binder.restoreCallingIdentity(token); - } - } - public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) { Objects.requireNonNull(router, "router must not be null"); if (TextUtils.isEmpty(packageName)) { @@ -418,7 +376,9 @@ class MediaRouter2ServiceImpl { } } - // Methods that implement MediaRouter2Manager operations. + // End of methods that implement MediaRouter2 operations. + + // Start of methods that implement MediaRouter2Manager operations. @NonNull public List<RoutingSessionInfo> getRemoteSessions(@NonNull IMediaRouter2Manager manager) { @@ -610,6 +570,52 @@ class MediaRouter2ServiceImpl { } } + // End of methods that implement MediaRouter2Manager operations. + + // Start of methods that implements operations for both MediaRouter2 and MediaRouter2Manager. + + @NonNull + public RoutingSessionInfo getSystemSessionInfo( + @Nullable String packageName, boolean setDeviceRouteSelected) { + final int uid = Binder.getCallingUid(); + final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED; + + final long token = Binder.clearCallingIdentity(); + try { + RoutingSessionInfo systemSessionInfo = null; + synchronized (mLock) { + UserRecord userRecord = getOrCreateUserRecordLocked(userId); + List<RoutingSessionInfo> sessionInfos; + if (hasModifyAudioRoutingPermission) { + if (setDeviceRouteSelected) { + systemSessionInfo = userRecord.mHandler.mSystemProvider + .generateDeviceRouteSelectedSessionInfo(packageName); + } else { + sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos(); + if (sessionInfos != null && !sessionInfos.isEmpty()) { + systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0)) + .setClientPackageName(packageName).build(); + } else { + Slog.w(TAG, "System provider does not have any session info."); + } + } + } else { + systemSessionInfo = new RoutingSessionInfo.Builder( + userRecord.mHandler.mSystemProvider.getDefaultSessionInfo()) + .setClientPackageName(packageName).build(); + } + } + return systemSessionInfo; + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager. + public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { pw.println(prefix + "MediaRouter2ServiceImpl"); @@ -678,6 +684,8 @@ class MediaRouter2ServiceImpl { return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId; } + // Start of locked methods that are used by MediaRouter2. + @GuardedBy("mLock") private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid, @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, @@ -952,6 +960,10 @@ class MediaRouter2ServiceImpl { DUMMY_REQUEST_ID, routerRecord, uniqueSessionId)); } + // End of locked methods that are used by MediaRouter2. + + // Start of locked methods that are used by MediaRouter2Manager. + private List<RoutingSessionInfo> getRemoteSessionsLocked( @NonNull IMediaRouter2Manager manager) { final IBinder binder = manager.asBinder(); @@ -1260,6 +1272,10 @@ class MediaRouter2ServiceImpl { uniqueRequestId, routerRecord, uniqueSessionId)); } + // End of locked methods that are used by MediaRouter2Manager. + + // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager. + @GuardedBy("mLock") private UserRecord getOrCreateUserRecordLocked(int userId) { UserRecord userRecord = mUserRecords.get(userId); @@ -1294,6 +1310,8 @@ class MediaRouter2ServiceImpl { } } + // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager. + static long toUniqueRequestId(int requesterId, int originalRequestId) { return ((long) requesterId << 32) | originalRequestId; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index eb37cebfb9e6..5dcbb162490a 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3422,6 +3422,8 @@ public class NotificationManagerService extends SystemService { @Override public void setToastRateLimitingEnabled(boolean enable) { + super.setToastRateLimitingEnabled_enforcePermission(); + synchronized (mToastQueue) { int uid = Binder.getCallingUid(); int userId = UserHandle.getUserId(uid); diff --git a/services/core/java/com/android/server/oemlock/OemLockService.java b/services/core/java/com/android/server/oemlock/OemLockService.java index 6735d55bf518..bac89160ea5d 100644 --- a/services/core/java/com/android/server/oemlock/OemLockService.java +++ b/services/core/java/com/android/server/oemlock/OemLockService.java @@ -120,6 +120,8 @@ public class OemLockService extends SystemService { @Nullable @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE) public String getLockName() { + super.getLockName_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { return mOemLock.getLockName(); @@ -131,6 +133,8 @@ public class OemLockService extends SystemService { @Override @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE) public void setOemUnlockAllowedByCarrier(boolean allowed, @Nullable byte[] signature) { + super.setOemUnlockAllowedByCarrier_enforcePermission(); + enforceUserIsAdmin(); final long token = Binder.clearCallingIdentity(); @@ -144,6 +148,8 @@ public class OemLockService extends SystemService { @Override @EnforcePermission(MANAGE_CARRIER_OEM_UNLOCK_STATE) public boolean isOemUnlockAllowedByCarrier() { + super.isOemUnlockAllowedByCarrier_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { return mOemLock.isOemUnlockAllowedByCarrier(); @@ -157,6 +163,8 @@ public class OemLockService extends SystemService { @Override @EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE) public void setOemUnlockAllowedByUser(boolean allowedByUser) { + super.setOemUnlockAllowedByUser_enforcePermission(); + if (ActivityManager.isUserAMonkey()) { // Prevent a monkey from changing this return; @@ -183,6 +191,8 @@ public class OemLockService extends SystemService { @Override @EnforcePermission(MANAGE_USER_OEM_UNLOCK_STATE) public boolean isOemUnlockAllowedByUser() { + super.isOemUnlockAllowedByUser_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { return mOemLock.isOemUnlockAllowedByDevice(); @@ -199,6 +209,8 @@ public class OemLockService extends SystemService { @Override @EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE}) public boolean isOemUnlockAllowed() { + super.isOemUnlockAllowed_enforcePermission(); + final long token = Binder.clearCallingIdentity(); try { boolean allowed = mOemLock.isOemUnlockAllowedByCarrier() @@ -213,6 +225,8 @@ public class OemLockService extends SystemService { @Override @EnforcePermission(anyOf = {READ_OEM_UNLOCK_STATE, OEM_UNLOCK_STATE}) public boolean isDeviceOemUnlocked() { + super.isDeviceOemUnlocked_enforcePermission(); + String locked = SystemProperties.get(FLASH_LOCK_PROP); switch (locked) { case FLASH_LOCK_UNLOCKED: diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index baa471c297d3..bb37e0e16a27 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -55,6 +55,7 @@ import android.content.om.OverlayableInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.content.res.ApkAssets; import android.net.Uri; @@ -887,7 +888,7 @@ public final class OverlayManagerService extends SystemService { } } - private Set<PackageAndUser> executeRequest( + private Set<UserPackage> executeRequest( @NonNull final OverlayManagerTransaction.Request request) throws OperationFailedException { Objects.requireNonNull(request, "Transaction contains a null request"); @@ -932,7 +933,7 @@ public final class OverlayManagerService extends SystemService { try { switch (request.type) { case TYPE_SET_ENABLED: - Set<PackageAndUser> result = null; + Set<UserPackage> result = null; result = CollectionUtils.addAll(result, mImpl.setEnabled(request.overlay, true, realUserId)); result = CollectionUtils.addAll(result, @@ -973,7 +974,7 @@ public final class OverlayManagerService extends SystemService { synchronized (mLock) { // execute the requests (as calling user) - Set<PackageAndUser> affectedPackagesToUpdate = null; + Set<UserPackage> affectedPackagesToUpdate = null; for (final OverlayManagerTransaction.Request request : transaction) { affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate, executeRequest(request)); @@ -1370,13 +1371,13 @@ public final class OverlayManagerService extends SystemService { } } - private void updateTargetPackagesLocked(@Nullable PackageAndUser updatedTarget) { + private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) { if (updatedTarget != null) { updateTargetPackagesLocked(Set.of(updatedTarget)); } } - private void updateTargetPackagesLocked(@Nullable Set<PackageAndUser> updatedTargets) { + private void updateTargetPackagesLocked(@Nullable Set<UserPackage> updatedTargets) { if (CollectionUtils.isEmpty(updatedTargets)) { return; } @@ -1405,7 +1406,7 @@ public final class OverlayManagerService extends SystemService { @Nullable private static SparseArray<ArraySet<String>> groupTargetsByUserId( - @Nullable final Set<PackageAndUser> targetsAndUsers) { + @Nullable final Set<UserPackage> targetsAndUsers) { final SparseArray<ArraySet<String>> userTargets = new SparseArray<>(); CollectionUtils.forEach(targetsAndUsers, target -> { ArraySet<String> targets = userTargets.get(target.userId); @@ -1472,7 +1473,7 @@ public final class OverlayManagerService extends SystemService { @NonNull private SparseArray<List<String>> updatePackageManagerLocked( - @Nullable Set<PackageAndUser> targets) { + @Nullable Set<UserPackage> targets) { if (CollectionUtils.isEmpty(targets)) { return new SparseArray<>(); } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index 17bb39c945bd..6ffe60d528ec 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -35,6 +35,7 @@ import android.annotation.Nullable; import android.content.om.CriticalOverlayInfo; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; +import android.content.pm.UserPackage; import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.os.FabricatedOverlayInfo; @@ -154,13 +155,13 @@ final class OverlayManagerServiceImpl { * set of targets that had, but no longer have, active overlays. */ @NonNull - ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) { + ArraySet<UserPackage> updateOverlaysForUser(final int newUserId) { if (DEBUG) { Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId); } // Remove the settings of all overlays that are no longer installed for this user. - final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>(); + final ArraySet<UserPackage> updatedTargets = new ArraySet<>(); final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser( newUserId); CollectionUtils.addAll(updatedTargets, removeOverlaysForUser( @@ -185,7 +186,7 @@ final class OverlayManagerServiceImpl { // When a new user is switched to for the first time, package manager must be // informed of the overlay paths for all overlaid packages installed in the user. if (overlaidByOthers.contains(pkg.getPackageName())) { - updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId)); + updatedTargets.add(UserPackage.of(newUserId, pkg.getPackageName())); } } catch (OperationFailedException e) { Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName() @@ -237,7 +238,7 @@ final class OverlayManagerServiceImpl { mSettings.setEnabled(overlay, newUserId, true); if (updateState(oi, newUserId, 0)) { CollectionUtils.add(updatedTargets, - new PackageAndUser(oi.targetPackageName, oi.userId)); + UserPackage.of(oi.userId, oi.targetPackageName)); } } } catch (OverlayManagerSettings.BadKeyException e) { @@ -258,40 +259,40 @@ final class OverlayManagerServiceImpl { } @NonNull - Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName, + Set<UserPackage> onPackageAdded(@NonNull final String pkgName, final int userId) throws OperationFailedException { - final Set<PackageAndUser> updatedTargets = new ArraySet<>(); + final Set<UserPackage> updatedTargets = new ArraySet<>(); // Always update the overlays of newly added packages. - updatedTargets.add(new PackageAndUser(pkgName, userId)); + updatedTargets.add(UserPackage.of(userId, pkgName)); updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */)); return updatedTargets; } @NonNull - Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName, + Set<UserPackage> onPackageChanged(@NonNull final String pkgName, final int userId) throws OperationFailedException { return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */); } @NonNull - Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId) + Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, final int userId) throws OperationFailedException { return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED); } @NonNull - Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId) + Set<UserPackage> onPackageReplaced(@NonNull final String pkgName, final int userId) throws OperationFailedException { return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */); } @NonNull - Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) { + Set<UserPackage> onPackageRemoved(@NonNull final String pkgName, final int userId) { if (DEBUG) { Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId); } // Update the state of all overlays that target this package. - final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */); + final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */); // Remove all the overlays this package declares. return CollectionUtils.addAll(targets, @@ -299,15 +300,15 @@ final class OverlayManagerServiceImpl { } @NonNull - private Set<PackageAndUser> removeOverlaysForUser( + private Set<UserPackage> removeOverlaysForUser( @NonNull final Predicate<OverlayInfo> condition, final int userId) { final List<OverlayInfo> overlays = mSettings.removeIf( io -> userId == io.userId && condition.test(io)); - Set<PackageAndUser> targets = Collections.emptySet(); + Set<UserPackage> targets = Collections.emptySet(); for (int i = 0, n = overlays.size(); i < n; i++) { final OverlayInfo info = overlays.get(i); targets = CollectionUtils.add(targets, - new PackageAndUser(info.targetPackageName, userId)); + UserPackage.of(userId, info.targetPackageName)); // Remove the idmap if the overlay is no longer installed for any user. removeIdmapIfPossible(info); @@ -316,7 +317,7 @@ final class OverlayManagerServiceImpl { } @NonNull - private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage, + private Set<UserPackage> updateOverlaysForTarget(@NonNull final String targetPackage, final int userId, final int flags) { boolean modified = false; final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId); @@ -332,18 +333,18 @@ final class OverlayManagerServiceImpl { if (!modified) { return Collections.emptySet(); } - return Set.of(new PackageAndUser(targetPackage, userId)); + return Set.of(UserPackage.of(userId, targetPackage)); } @NonNull - private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg, + private Set<UserPackage> updatePackageOverlays(@NonNull AndroidPackage pkg, final int userId, final int flags) throws OperationFailedException { if (pkg.getOverlayTarget() == null) { // This package does not have overlays declared in its manifest. return Collections.emptySet(); } - Set<PackageAndUser> updatedTargets = Collections.emptySet(); + Set<UserPackage> updatedTargets = Collections.emptySet(); final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName()); final int priority = getPackageConfiguredPriority(pkg); try { @@ -353,7 +354,7 @@ final class OverlayManagerServiceImpl { // If the targetPackageName has changed, the package that *used* to // be the target must also update its assets. updatedTargets = CollectionUtils.add(updatedTargets, - new PackageAndUser(currentInfo.targetPackageName, userId)); + UserPackage.of(userId, currentInfo.targetPackageName)); } currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(), @@ -367,13 +368,13 @@ final class OverlayManagerServiceImpl { // reinitialized. Reorder the overlay and update its target package. mSettings.setPriority(overlay, userId, priority); updatedTargets = CollectionUtils.add(updatedTargets, - new PackageAndUser(currentInfo.targetPackageName, userId)); + UserPackage.of(userId, currentInfo.targetPackageName)); } // Update the enabled state of the overlay. if (updateState(currentInfo, userId, flags)) { updatedTargets = CollectionUtils.add(updatedTargets, - new PackageAndUser(currentInfo.targetPackageName, userId)); + UserPackage.of(userId, currentInfo.targetPackageName)); } } catch (OverlayManagerSettings.BadKeyException e) { throw new OperationFailedException("failed to update settings", e); @@ -382,14 +383,14 @@ final class OverlayManagerServiceImpl { } @NonNull - private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName, + private Set<UserPackage> reconcileSettingsForPackage(@NonNull final String pkgName, final int userId, final int flags) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId); } // Update the state of overlays that target this package. - Set<PackageAndUser> updatedTargets = Collections.emptySet(); + Set<UserPackage> updatedTargets = Collections.emptySet(); updatedTargets = CollectionUtils.addAll(updatedTargets, updateOverlaysForTarget(pkgName, userId, flags)); @@ -423,7 +424,7 @@ final class OverlayManagerServiceImpl { } @NonNull - Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay, + Set<UserPackage> setEnabled(@NonNull final OverlayIdentifier overlay, final boolean enable, final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d", @@ -442,7 +443,7 @@ final class OverlayManagerServiceImpl { modified |= updateState(oi, userId, 0); if (modified) { - return Set.of(new PackageAndUser(oi.targetPackageName, userId)); + return Set.of(UserPackage.of(userId, oi.targetPackageName)); } return Set.of(); } catch (OverlayManagerSettings.BadKeyException e) { @@ -450,7 +451,7 @@ final class OverlayManagerServiceImpl { } } - Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay, + Optional<UserPackage> setEnabledExclusive(@NonNull final OverlayIdentifier overlay, boolean withinCategory, final int userId) throws OperationFailedException { if (DEBUG) { Slog.d(TAG, String.format("setEnabledExclusive overlay=%s" @@ -493,7 +494,7 @@ final class OverlayManagerServiceImpl { modified |= updateState(enabledInfo, userId, 0); if (modified) { - return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId)); + return Optional.of(UserPackage.of(userId, enabledInfo.targetPackageName)); } return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { @@ -502,7 +503,7 @@ final class OverlayManagerServiceImpl { } @NonNull - Set<PackageAndUser> registerFabricatedOverlay( + Set<UserPackage> registerFabricatedOverlay( @NonNull final FabricatedOverlayInternal overlay) throws OperationFailedException { if (FrameworkParsingPackageUtils.validateName(overlay.overlayName, @@ -516,7 +517,7 @@ final class OverlayManagerServiceImpl { throw new OperationFailedException("failed to create fabricated overlay"); } - final Set<PackageAndUser> updatedTargets = new ArraySet<>(); + final Set<UserPackage> updatedTargets = new ArraySet<>(); for (int userId : mSettings.getUsers()) { updatedTargets.addAll(registerFabricatedOverlay(info, userId)); } @@ -524,13 +525,13 @@ final class OverlayManagerServiceImpl { } @NonNull - private Set<PackageAndUser> registerFabricatedOverlay( + private Set<UserPackage> registerFabricatedOverlay( @NonNull final FabricatedOverlayInfo info, int userId) throws OperationFailedException { final OverlayIdentifier overlayIdentifier = new OverlayIdentifier( info.packageName, info.overlayName); - final Set<PackageAndUser> updatedTargets = new ArraySet<>(); + final Set<UserPackage> updatedTargets = new ArraySet<>(); OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId); if (oi != null) { if (!oi.isFabricated) { @@ -543,7 +544,7 @@ final class OverlayManagerServiceImpl { if (oi != null) { // If the fabricated overlay changes its target package, update the previous // target package so it no longer is overlaid. - updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId)); + updatedTargets.add(UserPackage.of(userId, oi.targetPackageName)); } oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName, info.targetOverlayable, info.path, true, false, @@ -554,7 +555,7 @@ final class OverlayManagerServiceImpl { mSettings.setBaseCodePath(overlayIdentifier, userId, info.path); } if (updateState(oi, userId, 0)) { - updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId)); + updatedTargets.add(UserPackage.of(userId, oi.targetPackageName)); } } catch (OverlayManagerSettings.BadKeyException e) { throw new OperationFailedException("failed to update settings", e); @@ -564,8 +565,8 @@ final class OverlayManagerServiceImpl { } @NonNull - Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) { - final Set<PackageAndUser> updatedTargets = new ArraySet<>(); + Set<UserPackage> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) { + final Set<UserPackage> updatedTargets = new ArraySet<>(); for (int userId : mSettings.getUsers()) { updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId)); } @@ -573,7 +574,7 @@ final class OverlayManagerServiceImpl { } @NonNull - private Set<PackageAndUser> unregisterFabricatedOverlay( + private Set<UserPackage> unregisterFabricatedOverlay( @NonNull final OverlayIdentifier overlay, int userId) { final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId); if (oi != null) { @@ -581,7 +582,7 @@ final class OverlayManagerServiceImpl { if (oi.isEnabled()) { // Removing a fabricated overlay only changes the overlay path of a package if it is // currently enabled. - return Set.of(new PackageAndUser(oi.targetPackageName, userId)); + return Set.of(UserPackage.of(userId, oi.targetPackageName)); } } return Set.of(); @@ -627,7 +628,7 @@ final class OverlayManagerServiceImpl { return mOverlayConfig.isEnabled(overlay.getPackageName()); } - Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay, + Optional<UserPackage> setPriority(@NonNull final OverlayIdentifier overlay, @NonNull final OverlayIdentifier newParentOverlay, final int userId) throws OperationFailedException { try { @@ -644,7 +645,7 @@ final class OverlayManagerServiceImpl { } if (mSettings.setPriority(overlay, newParentOverlay, userId)) { - return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId)); + return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName)); } return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { @@ -652,7 +653,7 @@ final class OverlayManagerServiceImpl { } } - Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay, + Set<UserPackage> setHighestPriority(@NonNull final OverlayIdentifier overlay, final int userId) throws OperationFailedException { try{ if (DEBUG) { @@ -667,7 +668,7 @@ final class OverlayManagerServiceImpl { } if (mSettings.setHighestPriority(overlay, userId)) { - return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId)); + return Set.of(UserPackage.of(userId, overlayInfo.targetPackageName)); } return Set.of(); } catch (OverlayManagerSettings.BadKeyException e) { @@ -675,7 +676,7 @@ final class OverlayManagerServiceImpl { } } - Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay, + Optional<UserPackage> setLowestPriority(@NonNull final OverlayIdentifier overlay, final int userId) throws OperationFailedException { try{ if (DEBUG) { @@ -690,7 +691,7 @@ final class OverlayManagerServiceImpl { } if (mSettings.setLowestPriority(overlay, userId)) { - return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId)); + return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName)); } return Optional.empty(); } catch (OverlayManagerSettings.BadKeyException e) { diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java deleted file mode 100644 index 672ae2eb0fb2..000000000000 --- a/services/core/java/com/android/server/pm/ApexPackageInfo.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.pm; - -import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE; -import static com.android.server.pm.ApexManager.MATCH_FACTORY_PACKAGE; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.apex.ApexInfo; -import android.content.pm.PackageManager; -import android.util.ArrayMap; -import android.util.Pair; -import android.util.PrintWriterPrinter; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.Preconditions; -import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.parsing.pkg.AndroidPackageUtils; -import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; - -import java.io.File; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.concurrent.ExecutorService; - -/** - * A temporary holder to store PackageInfo for scanned apex packages. We will unify the scan/install - * flows of APK and APEX and PMS will be the only source of truth for all package information - * including both APK and APEX. This class will no longer be needed when the migration is done. - */ -class ApexPackageInfo { - public static final boolean ENABLE_FEATURE_SCAN_APEX = true; - - private static final String TAG = "ApexManager"; - private static final String VNDK_APEX_MODULE_NAME_PREFIX = "com.android.vndk."; - - private final Object mLock = new Object(); - - @GuardedBy("mLock") - private List<Pair<ApexInfo, AndroidPackage>> mAllPackagesCache; - - @Nullable - private final PackageManagerService mPackageManager; - - ApexPackageInfo() { - mPackageManager = null; - } - - ApexPackageInfo(@NonNull PackageManagerService pms) { - mPackageManager = pms; - } - - /** - * Called by package manager service to scan apex package files when device boots up. - * - * @param allPackages All apex packages to scan. - * @param packageParser The package parser to support apex package parsing and caching parsed - * results. - * @param executorService An executor to support parallel package parsing. - */ - List<ApexManager.ScanResult> scanApexPackages(ApexInfo[] allPackages, - @NonNull PackageParser2 packageParser, @NonNull ExecutorService executorService) { - synchronized (mLock) { - return scanApexPackagesInternalLocked(allPackages, packageParser, executorService); - } - } - - void notifyScanResult(List<ApexManager.ScanResult> scanResults) { - synchronized (mLock) { - notifyScanResultLocked(scanResults); - } - } - - /** - * Retrieves information about an APEX package. - * - * @param packageName the package name to look for. Note that this is the package name reported - * in the APK container manifest (i.e. AndroidManifest.xml), which might - * differ from the one reported in the APEX manifest (i.e. - * apex_manifest.json). - * @param flags the type of package to return. This may match to active packages - * and factory (pre-installed) packages. - * @return a PackageInfo object with the information about the package, or null if the package - * is not found. - */ - @Nullable - Pair<ApexInfo, AndroidPackage> getPackageInfo(String packageName, - @ApexManager.PackageInfoFlags int flags) { - synchronized (mLock) { - Preconditions.checkState(mAllPackagesCache != null, - "APEX packages have not been scanned"); - boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0; - boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0; - for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) { - final Pair<ApexInfo, AndroidPackage> pair = mAllPackagesCache.get(i); - var apexInfo = pair.first; - var pkg = pair.second; - if (!pkg.getPackageName().equals(packageName)) { - continue; - } - if ((matchActive && apexInfo.isActive) - || (matchFactory && apexInfo.isFactory)) { - return pair; - } - } - return null; - } - } - - /** - * Retrieves information about all active APEX packages. - * - * @return list containing information about different active packages. - */ - @NonNull - List<Pair<ApexInfo, AndroidPackage>> getActivePackages() { - synchronized (mLock) { - Preconditions.checkState(mAllPackagesCache != null, - "APEX packages have not been scanned"); - final List<Pair<ApexInfo, AndroidPackage>> activePackages = new ArrayList<>(); - for (int i = 0; i < mAllPackagesCache.size(); i++) { - final var pair = mAllPackagesCache.get(i); - if (pair.first.isActive) { - activePackages.add(pair); - } - } - return activePackages; - } - } - - /** - * Retrieves information about all pre-installed APEX packages. - * - * @return list containing information about different pre-installed packages. - */ - @NonNull - List<Pair<ApexInfo, AndroidPackage>> getFactoryPackages() { - synchronized (mLock) { - Preconditions.checkState(mAllPackagesCache != null, - "APEX packages have not been scanned"); - final List<Pair<ApexInfo, AndroidPackage>> factoryPackages = new ArrayList<>(); - for (int i = 0; i < mAllPackagesCache.size(); i++) { - final var pair = mAllPackagesCache.get(i); - if (pair.first.isFactory) { - factoryPackages.add(pair); - } - } - return factoryPackages; - } - } - - /** - * Retrieves information about all inactive APEX packages. - * - * @return list containing information about different inactive packages. - */ - @NonNull - List<Pair<ApexInfo, AndroidPackage>> getInactivePackages() { - synchronized (mLock) { - Preconditions.checkState(mAllPackagesCache != null, - "APEX packages have not been scanned"); - final List<Pair<ApexInfo, AndroidPackage>> inactivePackages = new ArrayList<>(); - for (int i = 0; i < mAllPackagesCache.size(); i++) { - final var pair = mAllPackagesCache.get(i); - if (!pair.first.isActive) { - inactivePackages.add(pair); - } - } - return inactivePackages; - } - } - - /** - * Checks if {@code packageName} is an apex package. - * - * @param packageName package to check. - * @return {@code true} if {@code packageName} is an apex package. - */ - boolean isApexPackage(String packageName) { - synchronized (mLock) { - Preconditions.checkState(mAllPackagesCache != null, - "APEX packages have not been scanned"); - for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) { - final var pair = mAllPackagesCache.get(i); - if (pair.second.getPackageName().equals(packageName)) { - return true; - } - } - } - return false; - } - - /** - * Called to update cached PackageInfo when installing rebootless APEX. - */ - void notifyPackageInstalled(ApexInfo apexInfo, PackageParser2 packageParser) - throws PackageManagerException { - final int flags = PackageManager.GET_META_DATA - | PackageManager.GET_SIGNING_CERTIFICATES - | PackageManager.GET_SIGNATURES; - final ParsedPackage parsedPackage = packageParser.parsePackage( - new File(apexInfo.modulePath), flags, /* useCaches= */ false); - notifyPackageInstalled(apexInfo, parsedPackage.hideAsFinal()); - } - - void notifyPackageInstalled(ApexInfo apexInfo, AndroidPackage pkg) { - final String packageName = pkg.getPackageName(); - synchronized (mLock) { - for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) { - var pair = mAllPackagesCache.get(i); - var oldApexInfo = pair.first; - var oldApexPkg = pair.second; - if (oldApexInfo.isActive && oldApexPkg.getPackageName().equals(packageName)) { - if (oldApexInfo.isFactory) { - oldApexInfo.isActive = false; - mAllPackagesCache.add(Pair.create(apexInfo, pkg)); - } else { - mAllPackagesCache.set(i, Pair.create(apexInfo, pkg)); - } - break; - } - } - } - } - - /** - * Dumps various state information to the provided {@link PrintWriter} object. - * - * @param pw the {@link PrintWriter} object to send information to. - * @param packageName a {@link String} containing a package name, or {@code null}. If set, only - * information about that specific package will be dumped. - */ - void dump(PrintWriter pw, @Nullable String packageName) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - synchronized (mLock) { - if (mAllPackagesCache == null) { - ipw.println("APEX packages have not been scanned"); - return; - } - } - ipw.println("Active APEX packages:"); - dumpPackages(getActivePackages(), packageName, ipw); - ipw.println("Inactive APEX packages:"); - dumpPackages(getInactivePackages(), packageName, ipw); - ipw.println("Factory APEX packages:"); - dumpPackages(getFactoryPackages(), packageName, ipw); - } - - @GuardedBy("mLock") - private void notifyScanResultLocked(List<ApexManager.ScanResult> scanResults) { - mAllPackagesCache = new ArrayList<>(); - final int flags = PackageManager.GET_META_DATA - | PackageManager.GET_SIGNING_CERTIFICATES - | PackageManager.GET_SIGNATURES; - - HashSet<String> activePackagesSet = new HashSet<>(); - HashSet<String> factoryPackagesSet = new HashSet<>(); - for (ApexManager.ScanResult result : scanResults) { - ApexInfo ai = result.apexInfo; - String packageName = result.pkg.getPackageName(); - if (!packageName.equals(result.packageName)) { - throw new IllegalStateException("Unmatched package name: " - + result.packageName + " != " + packageName - + ", path=" + ai.modulePath); - } - mAllPackagesCache.add(Pair.create(ai, result.pkg)); - if (ai.isActive) { - if (!activePackagesSet.add(packageName)) { - throw new IllegalStateException( - "Two active packages have the same name: " + packageName); - } - } - if (ai.isFactory) { - // Don't throw when the duplicating APEX is VNDK APEX - if (!factoryPackagesSet.add(packageName) - && !ai.moduleName.startsWith(VNDK_APEX_MODULE_NAME_PREFIX)) { - throw new IllegalStateException( - "Two factory packages have the same name: " + packageName); - } - } - } - } - - @GuardedBy("mLock") - private List<ApexManager.ScanResult> scanApexPackagesInternalLocked(final ApexInfo[] allPkgs, - PackageParser2 packageParser, ExecutorService executorService) { - if (allPkgs == null || allPkgs.length == 0) { - notifyScanResultLocked(Collections.EMPTY_LIST); - return Collections.EMPTY_LIST; - } - - ArrayMap<File, ApexInfo> parsingApexInfo = new ArrayMap<>(); - ParallelPackageParser parallelPackageParser = - new ParallelPackageParser(packageParser, executorService); - for (ApexInfo ai : allPkgs) { - File apexFile = new File(ai.modulePath); - parallelPackageParser.submit(apexFile, - ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES); - parsingApexInfo.put(apexFile, ai); - } - - List<ApexManager.ScanResult> results = new ArrayList<>(parsingApexInfo.size()); - // Process results one by one - for (int i = 0; i < parsingApexInfo.size(); i++) { - ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take(); - Throwable throwable = parseResult.throwable; - ApexInfo ai = parsingApexInfo.get(parseResult.scanFile); - - if (throwable == null) { - // TODO: When ENABLE_FEATURE_SCAN_APEX is finalized, remove this and the entire - // calling path code - ScanPackageUtils.applyPolicy(parseResult.parsedPackage, - PackageManagerService.SCAN_AS_SYSTEM, - mPackageManager == null ? null : mPackageManager.getPlatformPackage(), - false); - // Calling hideAsFinal to assign derived fields for the app info flags. - AndroidPackage finalPkg = parseResult.parsedPackage.hideAsFinal(); - results.add(new ApexManager.ScanResult(ai, finalPkg, finalPkg.getPackageName())); - } else if (throwable instanceof PackageManagerException) { - throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable); - } else { - throw new IllegalStateException("Unexpected exception occurred while parsing " - + ai.modulePath, throwable); - } - } - - notifyScanResultLocked(results); - return results; - } - - /** - * @see #dumpPackages(List, String, IndentingPrintWriter) - */ - static void dumpPackageStates(List<PackageStateInternal> packageStates, boolean isActive, - @Nullable String packageName, IndentingPrintWriter ipw) { - ipw.println(); - ipw.increaseIndent(); - for (int i = 0, size = packageStates.size(); i < size; i++) { - final var packageState = packageStates.get(i); - var pkg = packageState.getPkg(); - if (packageName != null && !packageName.equals(pkg.getPackageName())) { - continue; - } - ipw.println(pkg.getPackageName()); - ipw.increaseIndent(); - ipw.println("Version: " + pkg.getLongVersionCode()); - ipw.println("Path: " + pkg.getBaseApkPath()); - ipw.println("IsActive: " + isActive); - ipw.println("IsFactory: " + !packageState.isUpdatedSystemApp()); - ipw.println("ApplicationInfo: "); - ipw.increaseIndent(); - // TODO: Dump the package manually - AndroidPackageUtils.generateAppInfoWithoutState(pkg) - .dump(new PrintWriterPrinter(ipw), ""); - ipw.decreaseIndent(); - ipw.decreaseIndent(); - } - ipw.decreaseIndent(); - ipw.println(); - } - - /** - * Dump information about the packages contained in a particular cache - * @param packagesCache the cache to print information about. - * @param packageName a {@link String} containing a package name, or {@code null}. If set, - * only information about that specific package will be dumped. - * @param ipw the {@link IndentingPrintWriter} object to send information to. - */ - static void dumpPackages(List<Pair<ApexInfo, AndroidPackage>> packagesCache, - @Nullable String packageName, IndentingPrintWriter ipw) { - ipw.println(); - ipw.increaseIndent(); - for (int i = 0, size = packagesCache.size(); i < size; i++) { - final var pair = packagesCache.get(i); - var apexInfo = pair.first; - var pkg = pair.second; - if (packageName != null && !packageName.equals(pkg.getPackageName())) { - continue; - } - ipw.println(pkg.getPackageName()); - ipw.increaseIndent(); - ipw.println("Version: " + pkg.getLongVersionCode()); - ipw.println("Path: " + pkg.getBaseApkPath()); - ipw.println("IsActive: " + apexInfo.isActive); - ipw.println("IsFactory: " + apexInfo.isFactory); - ipw.println("ApplicationInfo: "); - ipw.increaseIndent(); - // TODO: Dump the package manually - AndroidPackageUtils.generateAppInfoWithoutState(pkg) - .dump(new PrintWriterPrinter(ipw), ""); - ipw.decreaseIndent(); - ipw.decreaseIndent(); - } - ipw.decreaseIndent(); - ipw.println(); - } -}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index b8e1e9ae1832..f3cfa95ff86d 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -61,7 +61,7 @@ import java.util.concurrent.Future; /** * Prepares app data for users */ -final class AppDataHelper { +public class AppDataHelper { private static final boolean DEBUG_APP_DATA = false; private final PackageManagerService mPm; diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index 4c21195e2890..2e67bf2fcdf7 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -22,6 +22,15 @@ import static android.os.UserHandle.USER_NULL; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__BOOT; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_CREATED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_DELETED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__COMPAT_CHANGED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_ADDED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED; +import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED; import static com.android.server.pm.AppsFilterUtils.canQueryAsInstaller; import static com.android.server.pm.AppsFilterUtils.canQueryViaComponents; import static com.android.server.pm.AppsFilterUtils.canQueryViaPackage; @@ -36,6 +45,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.SigningDetails; import android.content.pm.UserInfo; import android.os.Handler; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; @@ -49,6 +59,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.FgThread; import com.android.server.compat.CompatChange; import com.android.server.om.OverlayReferenceMapper; @@ -351,8 +362,15 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, if (pkg == null) { return; } + final long currentTimeUs = SystemClock.currentTimeMicro(); updateEnabledState(pkg); mAppsFilter.updateShouldFilterCacheForPackage(snapshot, packageName); + mAppsFilter.logCacheUpdated( + PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__COMPAT_CHANGED, + SystemClock.currentTimeMicro() - currentTimeUs, + snapshot.getUserInfos().length, + snapshot.getPackageStates().size(), + pkg.getUid()); } private void updateEnabledState(@NonNull AndroidPackage pkg) { @@ -465,7 +483,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, mOverlayReferenceMapper.rebuildIfDeferred(); mFeatureConfig.onSystemReady(); - updateEntireShouldFilterCacheAsync(pmInternal); + updateEntireShouldFilterCacheAsync(pmInternal, + PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__BOOT); } /** @@ -473,16 +492,23 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, * * @param newPkgSetting the new setting being added * @param isReplace if the package is being replaced and may need extra cleanup. + * @param retainImplicitGrantOnReplace {@code true} to retain implicit grant access if + * the package is being replaced. */ public void addPackage(Computer snapshot, PackageStateInternal newPkgSetting, - boolean isReplace) { + boolean isReplace, boolean retainImplicitGrantOnReplace) { + final long currentTimeUs = SystemClock.currentTimeMicro(); + final int logType = isReplace + ? PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED + : PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_ADDED; if (DEBUG_TRACING) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); } try { if (isReplace) { // let's first remove any prior rules for this package - removePackage(snapshot, newPkgSetting, true /*isReplace*/); + removePackageInternal(snapshot, newPkgSetting, + true /*isReplace*/, retainImplicitGrantOnReplace); } final ArrayMap<String, ? extends PackageStateInternal> settings = snapshot.getPackageStates(); @@ -508,6 +534,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, } } } + logCacheUpdated(logType, SystemClock.currentTimeMicro() - currentTimeUs, + users.length, settings.size(), newPkgSetting.getAppId()); } else { invalidateCache("addPackage: " + newPkgSetting.getPackageName()); } @@ -757,18 +785,19 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, } } - private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal) { - updateEntireShouldFilterCacheAsync(pmInternal, CACHE_REBUILD_DELAY_MIN_MS); + private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal, int reason) { + updateEntireShouldFilterCacheAsync(pmInternal, CACHE_REBUILD_DELAY_MIN_MS, reason); } private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal, - long delayMs) { + long delayMs, int reason) { mBackgroundHandler.postDelayed(() -> { if (!mCacheValid.compareAndSet(CACHE_INVALID, CACHE_VALID)) { // Cache is already valid. return; } + final long currentTimeUs = SystemClock.currentTimeMicro(); final ArrayMap<String, AndroidPackage> packagesCache = new ArrayMap<>(); final UserInfo[][] usersRef = new UserInfo[1][]; final Computer snapshot = (Computer) pmInternal.snapshot(); @@ -787,11 +816,13 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL); onChanged(); + logCacheRebuilt(reason, SystemClock.currentTimeMicro() - currentTimeUs, + users.length, settings.size()); if (!mCacheValid.compareAndSet(CACHE_VALID, CACHE_VALID)) { Slog.i(TAG, "Cache invalidated while building, retrying."); updateEntireShouldFilterCacheAsync(pmInternal, - Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS)); + Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS), reason); return; } @@ -803,15 +834,27 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, if (!mCacheReady) { return; } + final long currentTimeUs = SystemClock.currentTimeMicro(); updateEntireShouldFilterCache(snapshot, newUserId); + logCacheRebuilt( + PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_CREATED, + SystemClock.currentTimeMicro() - currentTimeUs, + snapshot.getUserInfos().length, + snapshot.getPackageStates().size()); } - public void onUserDeleted(@UserIdInt int userId) { + public void onUserDeleted(Computer snapshot, @UserIdInt int userId) { if (!mCacheReady) { return; } + final long currentTimeUs = SystemClock.currentTimeMicro(); removeShouldFilterCacheForUser(userId); onChanged(); + logCacheRebuilt( + PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_DELETED, + SystemClock.currentTimeMicro() - currentTimeUs, + snapshot.getUserInfos().length, + snapshot.getPackageStates().size()); } private void updateShouldFilterCacheForPackage(Computer snapshot, @@ -976,13 +1019,31 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, } /** - * Equivalent to calling {@link #addPackage(Computer, PackageStateInternal, boolean)} - * with {@code isReplace} equal to {@code false}. + * Equivalent to calling {@link #addPackage(Computer, PackageStateInternal, boolean, boolean)} + * with {@code isReplace} and {@code retainImplicitGrantOnReplace} equal to {@code false}. * - * @see AppsFilterImpl#addPackage(Computer, PackageStateInternal, boolean) + * @see AppsFilterImpl#addPackage(Computer, PackageStateInternal, boolean, boolean) */ public void addPackage(Computer snapshot, PackageStateInternal newPkgSetting) { - addPackage(snapshot, newPkgSetting, false /* isReplace */); + addPackage(snapshot, newPkgSetting, false /* isReplace */, + false /* retainImplicitGrantOnReplace */); + } + + /** + * Removes a package for consideration when filtering visibility between apps. + * + * @param setting the setting of the package being removed. + */ + public void removePackage(Computer snapshot, PackageStateInternal setting) { + final long currentTimeUs = SystemClock.currentTimeMicro(); + removePackageInternal(snapshot, setting, + false /* isReplace */, false /* retainImplicitGrantOnReplace */); + logCacheUpdated( + PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED, + SystemClock.currentTimeMicro() - currentTimeUs, + snapshot.getUserInfos().length, + snapshot.getPackageStates().size(), + setting.getAppId()); } /** @@ -990,33 +1051,37 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, * * @param setting the setting of the package being removed. * @param isReplace if the package is being replaced. + * @param retainImplicitGrantOnReplace {@code true} to retain implicit grant access if + * the package is being replaced. */ - public void removePackage(Computer snapshot, PackageStateInternal setting, - boolean isReplace) { + private void removePackageInternal(Computer snapshot, PackageStateInternal setting, + boolean isReplace, boolean retainImplicitGrantOnReplace) { final ArraySet<String> additionalChangedPackages; final ArrayMap<String, ? extends PackageStateInternal> settings = snapshot.getPackageStates(); final UserInfo[] users = snapshot.getUserInfos(); final Collection<SharedUserSetting> sharedUserSettings = snapshot.getAllSharedUsers(); final int userCount = users.length; - synchronized (mImplicitlyQueryableLock) { - for (int u = 0; u < userCount; u++) { - final int userId = users[u].id; - final int removingUid = UserHandle.getUid(userId, setting.getAppId()); - mImplicitlyQueryable.remove(removingUid); - for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) { - mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), - removingUid); - } + if (!isReplace || !retainImplicitGrantOnReplace) { + synchronized (mImplicitlyQueryableLock) { + for (int u = 0; u < userCount; u++) { + final int userId = users[u].id; + final int removingUid = UserHandle.getUid(userId, setting.getAppId()); + mImplicitlyQueryable.remove(removingUid); + for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) { + mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i), + removingUid); + } - if (isReplace) { - continue; - } + if (isReplace) { + continue; + } - mRetainedImplicitlyQueryable.remove(removingUid); - for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) { - mRetainedImplicitlyQueryable.remove( - mRetainedImplicitlyQueryable.keyAt(i), removingUid); + mRetainedImplicitlyQueryable.remove(removingUid); + for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) { + mRetainedImplicitlyQueryable.remove( + mRetainedImplicitlyQueryable.keyAt(i), removingUid); + } } } } @@ -1174,4 +1239,18 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, } } } + + private void logCacheRebuilt(int eventId, long latency, int userCount, int packageCount) { + FrameworkStatsLog.write(PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED, + eventId, latency, userCount, packageCount, mShouldFilterCache.size()); + } + + private void logCacheUpdated(int eventId, long latency, int userCount, int packageCount, + int appId) { + if (!mCacheReady) { + return; + } + FrameworkStatsLog.write(PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED, + eventId, appId, latency, userCount, packageCount, mShouldFilterCache.size()); + } } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index e7412c5e52ec..d856d54cb4e0 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -410,7 +410,7 @@ public final class BackgroundDexOptService { job.jobFinished(params, !completed); } else { // Periodic job - job.jobFinished(params, true); + job.jobFinished(params, false /* reschedule */); } markDexOptCompleted(); } diff --git a/services/core/java/com/android/server/pm/CloneProfileResolver.java b/services/core/java/com/android/server/pm/CloneProfileResolver.java new file mode 100644 index 000000000000..d3113e12abe3 --- /dev/null +++ b/services/core/java/com/android/server/pm/CloneProfileResolver.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.Intent; +import android.content.pm.ResolveInfo; + +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.resolution.ComponentResolverApi; +import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +/** + * Cross Profile intent resolution strategy used for and to clone profile. + */ +public class CloneProfileResolver extends CrossProfileResolver { + + public CloneProfileResolver(ComponentResolverApi componentResolver, + UserManagerService userManagerService) { + super(componentResolver, userManagerService); + } + + /** + * This is resolution strategy for Clone Profile. + * In case of clone profile, the profile is supposed to be transparent to end user. To end user + * clone and owner profile should be part of same user space. Hence, the resolution strategy + * would resolve intent in both profile and return combined result without any filtering of the + * results. + * + * @param computer ComputerEngine instance that would be needed by ComponentResolverApi + * @param intent request + * @param resolvedType the MIME data type of intent request + * @param userId source/initiating user + * @param targetUserId target user id + * @param flags of intent request + * @param pkgName the application package name this Intent is limited to + * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user, + * targeting the targetUserId + * @param hasNonNegativePriorityResult if source have any non-negative(active and valid) + * resolveInfo in their profile. + * @param pkgSettingFunction function to find PackageStateInternal for given package + * @return list of {@link CrossProfileDomainInfo} + */ + @Override + public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent, + String resolvedType, int userId, int targetUserId, long flags, + String pkgName, List<CrossProfileIntentFilter> matchingFilters, + boolean hasNonNegativePriorityResult, + Function<String, PackageStateInternal> pkgSettingFunction) { + List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer, + intent, resolvedType, flags, targetUserId); + List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>(); + if (resolveInfos != null) { + + for (int index = 0; index < resolveInfos.size(); index++) { + crossProfileDomainInfos.add(new CrossProfileDomainInfo(resolveInfos.get(index), + DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE, + targetUserId)); + } + } + return filterIfNotSystemUser(crossProfileDomainInfos, userId); + } + + /** + * As clone and owner profile are going to be part of the same userspace, we need no filtering + * out of any clone profile's result + * @param intent request + * @param crossProfileDomainInfos resolved in target user + * @param flags for intent resolution + * @param sourceUserId source user + * @param targetUserId target user + * @param highestApprovalLevel highest level of domain approval + * @return list of CrossProfileDomainInfo + */ + @Override + public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(Intent intent, + List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId, + int targetUserId, int highestApprovalLevel) { + // no filtering for clone profile + return crossProfileDomainInfos; + } +} diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index b9967f94876a..b2851369b56e 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -63,7 +63,6 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.apex.ApexInfo; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; @@ -112,6 +111,7 @@ import android.util.LogPrinter; import android.util.LongSparseLongArray; import android.util.MathUtils; import android.util.Pair; +import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -396,7 +396,6 @@ public class ComputerEngine implements Computer { private final UserManagerService mUserManager; private final PermissionManagerServiceInternal mPermissionManager; private final ApexManager mApexManager; - private final ApexPackageInfo mApexPackageInfo; private final PackageManagerServiceInjector mInjector; private final ComponentResolverApi mComponentResolver; private final InstantAppResolverConnection mInstantAppResolverConnection; @@ -452,7 +451,6 @@ public class ComputerEngine implements Computer { mContext = args.service.mContext; mInjector = args.service.mInjector; mApexManager = args.service.mApexManager; - mApexPackageInfo = args.service.mApexPackageInfo; mInstantAppResolverConnection = args.service.mInstantAppResolverConnection; mDefaultAppProvider = args.service.getDefaultAppProvider(); mDomainVerificationManager = args.service.mDomainVerificationManager; @@ -462,7 +460,7 @@ public class ComputerEngine implements Computer { mBackgroundDexOptService = args.service.mBackgroundDexOptService; mExternalSourcesPolicy = args.service.mExternalSourcesPolicy; mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine( - mUserManager, mDomainVerificationManager, mDefaultAppProvider); + mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext); // Used to reference PMS attributes that are primitives and which are not // updated under control of the PMS lock. @@ -968,10 +966,8 @@ public class ComputerEngine implements Computer { if (p != null) { PackageStateInternal ps = mSettings.getPackage(packageName); if (ps == null) return null; - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!matchApex && p.isApex()) { - return null; - } + if (!matchApex && p.isApex()) { + return null; } if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) { return null; @@ -987,24 +983,6 @@ public class ComputerEngine implements Computer { } return ai; } - if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (matchApex) { - // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo - // returned from getApplicationInfo, but for APEX packages difference shouldn't be - // very big. - // TODO(b/155328545): generate proper application info for APEXes as well. - int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE; - if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) { - apexFlags = ApexManager.MATCH_FACTORY_PACKAGE; - } - final var pair = mApexPackageInfo.getPackageInfo(packageName, apexFlags); - if (pair == null) { - return null; - } - return PackageInfoUtils.generateApplicationInfo(pair.second, flags, - PackageUserStateInternal.DEFAULT, userId, null); - } - } if ("android".equals(packageName) || "system".equals(packageName)) { return androidApplication(); } @@ -1553,22 +1531,10 @@ public class ComputerEngine implements Computer { final boolean matchApex = (flags & MATCH_APEX) != 0; if (matchFactoryOnly) { // Instant app filtering for APEX modules is ignored - if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (matchApex) { - final var pair = mApexPackageInfo.getPackageInfo(packageName, - ApexManager.MATCH_FACTORY_PACKAGE); - if (pair == null) { - return null; - } - return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId); - } - } final PackageStateInternal ps = mSettings.getDisabledSystemPkg(packageName); if (ps != null) { - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!matchApex && ps.getPkg() != null && ps.getPkg().isApex()) { - return null; - } + if (!matchApex && ps.getPkg() != null && ps.getPkg().isApex()) { + return null; } if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) { return null; @@ -1589,10 +1555,8 @@ public class ComputerEngine implements Computer { } if (p != null) { final PackageStateInternal ps = getPackageStateInternal(p.getPackageName()); - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!matchApex && p.isApex()) { - return null; - } + if (!matchApex && p.isApex()) { + return null; } if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) { return null; @@ -1614,16 +1578,6 @@ public class ComputerEngine implements Computer { } return generatePackageInfo(ps, flags, userId); } - if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (matchApex) { - final var pair = mApexPackageInfo.getPackageInfo(packageName, - ApexManager.MATCH_ACTIVE_PACKAGE); - if (pair == null) { - return null; - } - return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId); - } - } return null; } @@ -1692,10 +1646,8 @@ public class ComputerEngine implements Computer { ps = psDisabled; } } - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) { - continue; - } + if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) { + continue; } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { continue; @@ -1722,10 +1674,8 @@ public class ComputerEngine implements Computer { ps = psDisabled; } } - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!listApex && p.isApex()) { - continue; - } + if (!listApex && p.isApex()) { + continue; } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { continue; @@ -1739,22 +1689,6 @@ public class ComputerEngine implements Computer { } } } - if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (listApex) { - List<Pair<ApexInfo, AndroidPackage>> pairs; - if (listFactory) { - pairs = mApexPackageInfo.getFactoryPackages(); - } else { - pairs = mApexPackageInfo.getActivePackages(); - } - - for (int index = 0; index < pairs.size(); index++) { - var pair = pairs.get(index); - list.add(PackageInfoUtils.generate(pair.second, pair.first, flags, null, - userId)); - } - } - } return new ParceledListSlice<>(list); } @@ -3155,24 +3089,56 @@ public class ComputerEngine implements Computer { } private void dumpApex(PrintWriter pw, String packageName) { - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - List<PackageStateInternal> activePackages = new ArrayList<>(); - List<PackageStateInternal> inactivePackages = new ArrayList<>(); - List<PackageStateInternal> factoryActivePackages = new ArrayList<>(); - List<PackageStateInternal> factoryInactivePackages = new ArrayList<>(); - generateApexPackageInfo(activePackages, inactivePackages, factoryActivePackages, - factoryInactivePackages); - ipw.println("Active APEX packages:"); - ApexPackageInfo.dumpPackageStates(activePackages, true, packageName, ipw); - ipw.println("Inactive APEX packages:"); - ApexPackageInfo.dumpPackageStates(inactivePackages, false, packageName, ipw); - ipw.println("Factory APEX packages:"); - ApexPackageInfo.dumpPackageStates(factoryActivePackages, true, packageName, ipw); - ApexPackageInfo.dumpPackageStates(factoryInactivePackages, false, packageName, ipw); - } else { - mApexPackageInfo.dump(pw, packageName); + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + List<PackageStateInternal> activePackages = new ArrayList<>(); + List<PackageStateInternal> inactivePackages = new ArrayList<>(); + List<PackageStateInternal> factoryActivePackages = new ArrayList<>(); + List<PackageStateInternal> factoryInactivePackages = new ArrayList<>(); + generateApexPackageInfo(activePackages, inactivePackages, factoryActivePackages, + factoryInactivePackages); + ipw.println("Active APEX packages:"); + dumpApexPackageStates(activePackages, true, packageName, ipw); + ipw.println("Inactive APEX packages:"); + dumpApexPackageStates(inactivePackages, false, packageName, ipw); + ipw.println("Factory APEX packages:"); + dumpApexPackageStates(factoryActivePackages, true, packageName, ipw); + dumpApexPackageStates(factoryInactivePackages, false, packageName, ipw); + } + + + /** + * Dump information about the packages contained in a particular cache + * @param packageStates the states to print information about. + * @param packageName a {@link String} containing a package name, or {@code null}. If set, + * only information about that specific package will be dumped. + * @param ipw the {@link IndentingPrintWriter} object to send information to. + */ + private static void dumpApexPackageStates(List<PackageStateInternal> packageStates, + boolean isActive, @Nullable String packageName, IndentingPrintWriter ipw) { + ipw.println(); + ipw.increaseIndent(); + for (int i = 0, size = packageStates.size(); i < size; i++) { + final var packageState = packageStates.get(i); + var pkg = packageState.getPkg(); + if (packageName != null && !packageName.equals(pkg.getPackageName())) { + continue; + } + ipw.println(pkg.getPackageName()); + ipw.increaseIndent(); + ipw.println("Version: " + pkg.getLongVersionCode()); + ipw.println("Path: " + pkg.getBaseApkPath()); + ipw.println("IsActive: " + isActive); + ipw.println("IsFactory: " + !packageState.isUpdatedSystemApp()); + ipw.println("ApplicationInfo: "); + ipw.increaseIndent(); + // TODO: Dump the package manually + AndroidPackageUtils.generateAppInfoWithoutState(pkg) + .dump(new PrintWriterPrinter(ipw), ""); + ipw.decreaseIndent(); + ipw.decreaseIndent(); } + ipw.decreaseIndent(); + ipw.println(); } // The body of findPreferredActivity. @@ -3551,12 +3517,8 @@ public class ComputerEngine implements Computer { @Override public boolean isApexPackage(String packageName) { - if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - return mApexPackageInfo.isApexPackage(packageName); - } else { - final AndroidPackage pkg = mPackages.get(packageName); - return pkg != null && pkg.isApex(); - } + final AndroidPackage pkg = mPackages.get(packageName); + return pkg != null && pkg.isApex(); } @Override @@ -4563,10 +4525,8 @@ public class ComputerEngine implements Computer { effectiveFlags |= PackageManager.MATCH_ANY_USER; } if (ps.getPkg() != null) { - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!listApex && ps.getPkg().isApex()) { - continue; - } + if (!listApex && ps.getPkg().isApex()) { + continue; } if (filterSharedLibPackage(ps, callingUid, userId, flags)) { continue; @@ -4596,10 +4556,8 @@ public class ComputerEngine implements Computer { if (pkg == null) { continue; } - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - if (!listApex && pkg.isApex()) { - continue; - } + if (!listApex && pkg.isApex()) { + continue; } if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) { continue; diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java index 0cd698a5eb0a..798217f226cf 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java @@ -46,6 +46,9 @@ class CrossProfileIntentFilter extends WatchedIntentFilter { private static final String ATTR_FILTER = "filter"; private static final String ATTR_ACCESS_CONTROL = "accessControl"; + //flag to decide if intent needs to be resolved cross profile if pkgName is already defined + public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008; + private static final String TAG = "CrossProfileIntentFilter"; /** diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java index 9ea16d322c3c..258187805542 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR_FILTER; + import android.annotation.NonNull; import android.content.IntentFilter; @@ -37,7 +39,7 @@ class CrossProfileIntentResolver @Override protected boolean isPackageForFilter(String packageName, CrossProfileIntentFilter filter) { - return false; + return (FLAG_IS_PACKAGE_FOR_FILTER & filter.mFlags) != 0; } @Override diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java index 7752fdffb1d7..5ae4cab8e7a6 100644 --- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java +++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java @@ -25,12 +25,14 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.os.Process; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -54,13 +56,15 @@ public class CrossProfileIntentResolverEngine { private final UserManagerService mUserManager; private final DomainVerificationManagerInternal mDomainVerificationManager; private final DefaultAppProvider mDefaultAppProvider; + private final Context mContext; public CrossProfileIntentResolverEngine(UserManagerService userManager, DomainVerificationManagerInternal domainVerificationManager, - DefaultAppProvider defaultAppProvider) { + DefaultAppProvider defaultAppProvider, Context context) { mUserManager = userManager; mDomainVerificationManager = domainVerificationManager; mDefaultAppProvider = defaultAppProvider; + mContext = context; } /** @@ -196,6 +200,21 @@ public class CrossProfileIntentResolverEngine { @SuppressWarnings("unused") private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer, UserInfo sourceUserInfo, UserInfo targetUserInfo) { + //todo change isCloneProfile to user properties b/241532322 + /** + * If source or target user is clone profile, using {@link CloneProfileResolver} + * We would allow CloneProfileResolver only if flag + * SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE is enabled + */ + if (sourceUserInfo.isCloneProfile() || targetUserInfo.isCloneProfile()) { + if (FeatureFlagUtils.isEnabled(mContext, + FeatureFlagUtils.SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE)) { + return new CloneProfileResolver(computer.getComponentResolver(), + mUserManager); + } else { + return null; + } + } return new DefaultCrossProfileResolver(computer.getComponentResolver(), mUserManager, mDomainVerificationManager); } @@ -313,8 +332,6 @@ public class CrossProfileIntentResolverEngine { } if (pkgName == null && intent.hasWebURI()) { - // If instant apps are not allowed and there is result only from current or cross - // profile return it if (!addInstant && ((candidates.size() <= 1 && crossProfileCandidates.isEmpty()) || (candidates.isEmpty() && !crossProfileCandidates.isEmpty()))) { candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates)); diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java index 6f5909615db2..12b5ab8a3160 100644 --- a/services/core/java/com/android/server/pm/InitAppsHelper.java +++ b/services/core/java/com/android/server/pm/InitAppsHelper.java @@ -72,7 +72,6 @@ final class InitAppsHelper { private final int mSystemScanFlags; private final InstallPackageHelper mInstallPackageHelper; private final ApexManager mApexManager; - private final ApexPackageInfo mApexPackageInfo; private final ExecutorService mExecutorService; /* Tracks how long system scan took */ private long mSystemScanTime; @@ -96,13 +95,11 @@ final class InitAppsHelper { private final List<String> mStubSystemApps = new ArrayList<>(); // TODO(b/198166813): remove PMS dependency - InitAppsHelper(PackageManagerService pm, - ApexManager apexManager, ApexPackageInfo apexPackageInfo, + InitAppsHelper(PackageManagerService pm, ApexManager apexManager, InstallPackageHelper installPackageHelper, List<ScanPartition> systemPartitions) { mPm = pm; mApexManager = apexManager; - mApexPackageInfo = apexPackageInfo; mInstallPackageHelper = installPackageHelper; mSystemPartitions = systemPartitions; mDirsToScanAsSystem = getSystemScanPartitions(); @@ -165,16 +162,8 @@ final class InitAppsHelper { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackages"); try { - final List<ApexManager.ScanResult> apexScanResults; - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - apexScanResults = mInstallPackageHelper.scanApexPackages( - mApexManager.getAllApexInfos(), mSystemParseFlags, mSystemScanFlags, - packageParser, mExecutorService); - } else { - apexScanResults = mApexPackageInfo.scanApexPackages( - mApexManager.getAllApexInfos(), packageParser, mExecutorService); - } - return apexScanResults; + return mInstallPackageHelper.scanApexPackages(mApexManager.getAllApexInfos(), + mSystemParseFlags, mSystemScanFlags, packageParser, mExecutorService); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 9d007c92bcc5..7ea0c04562e2 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -463,7 +463,8 @@ final class InstallPackageHelper { final Computer snapshot = mPm.snapshotComputer(); mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot); - mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace); + mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace, + (scanFlags & SCAN_DONT_KILL_APP) != 0 /* retainImplicitGrantOnReplace */); mPm.addAllPackageProperties(pkg); if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) { diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index 16b3a815c075..88469f539f94 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -572,19 +572,15 @@ class InstallingSession { } try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) { ApexInfo apexInfo = mPm.mApexManager.installPackage(apexes[0]); - if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) { - // APEX has been handled successfully by apexd. Let's continue the install flow - // so it will be scanned and registered with the system. - // TODO(b/225756739): Improve atomicity of rebootless APEX install. - // The newly installed APEX will not be reverted even if - // processApkInstallRequests() fails. Need a way to keep info stored in apexd - // and PMS in sync in the face of install failures. - request.setApexInfo(apexInfo); - mPm.mHandler.post(() -> processApkInstallRequests(true, requests)); - return; - } else { - mPm.mApexPackageInfo.notifyPackageInstalled(apexInfo, packageParser); - } + // APEX has been handled successfully by apexd. Let's continue the install flow + // so it will be scanned and registered with the system. + // TODO(b/225756739): Improve atomicity of rebootless APEX install. + // The newly installed APEX will not be reverted even if + // processApkInstallRequests() fails. Need a way to keep info stored in apexd + // and PMS in sync in the face of install failures. + request.setApexInfo(apexInfo); + mPm.mHandler.post(() -> processApkInstallRequests(true, requests)); + return; } } catch (PackageManagerException e) { request.setError("APEX installation failed", e); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 5df73a6d1fbd..72ec510031e2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2763,10 +2763,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Missing existing base package"); } - // Default to require only if existing base apk has fs-verity. + // Default to require only if existing base apk has fs-verity signature. mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled() && params.mode == SessionParams.MODE_INHERIT_EXISTING - && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath()); + && (new File(VerityUtils.getFsveritySignatureFilePath( + pkgInfo.applicationInfo.getBaseCodePath()))).exists(); final List<File> removedFiles = getRemovedFilesLocked(); final List<String> removeSplitList = new ArrayList<>(); @@ -3326,7 +3327,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { "Failure to obtain package info."); } final List<String> filePaths = packageLite.getAllApkPaths(); - final String appLabel = mPreapprovalDetails.getLabel(); + final CharSequence appLabel = mPreapprovalDetails.getLabel(); final ULocale appLocale = mPreapprovalDetails.getLocale(); final ApplicationInfo appInfo = packageInfo.applicationInfo; boolean appLabelMatched = false; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index dfbe68a8e997..b302d4aaae9c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -107,6 +107,7 @@ import android.content.pm.SigningDetails; import android.content.pm.SuspendDialogInfo; import android.content.pm.TestUtilityService; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VersionedPackage; import android.content.pm.overlay.OverlayPaths; @@ -690,7 +691,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService private final ModuleInfoProvider mModuleInfoProvider; final ApexManager mApexManager; - final ApexPackageInfo mApexPackageInfo; final PackageManagerServiceInjector mInjector; @@ -1147,7 +1147,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService var done = SystemClock.currentTimeMicro(); if (mSnapshotStatistics != null) { - mSnapshotStatistics.rebuild(now, done, hits); + mSnapshotStatistics.rebuild(now, done, hits, newSnapshot.getPackageStates().size()); } return newSnapshot; } @@ -1641,7 +1641,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService mSharedLibraries = injector.getSharedLibrariesImpl(); mApexManager = testParams.apexManager; - mApexPackageInfo = new ApexPackageInfo(this); mArtManagerService = testParams.artManagerService; mAvailableFeatures = testParams.availableFeatures; mBackgroundDexOptService = testParams.backgroundDexOptService; @@ -1841,7 +1840,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService mProtectedPackages = new ProtectedPackages(mContext); mApexManager = injector.getApexManager(); - mApexPackageInfo = new ApexPackageInfo(this); mAppsFilter = mInjector.getAppsFilter(); mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager, @@ -1979,8 +1977,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT); } - mInitAppsHelper = new InitAppsHelper(this, mApexManager, mApexPackageInfo, - mInstallPackageHelper, mInjector.getSystemPartitions()); + mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper, + mInjector.getSystemPartitions()); // when upgrading from pre-M, promote system app permissions from install to runtime mPromoteSystemApps = @@ -2989,6 +2987,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService @Override public void notifyPackageRemoved(String packageName, int uid) { mPackageObserverHelper.notifyRemoved(packageName, uid); + UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName); } void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName, @@ -4220,7 +4219,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mSettings.removeUserLPw(userId); mPendingBroadcasts.remove(userId); mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId); - mAppsFilter.onUserDeleted(userId); + mAppsFilter.onUserDeleted(snapshotComputer(), userId); } mInstantAppRegistry.onUserRemoved(userId); } diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index bbc4fdeb36bb..7e936735ef7d 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -308,7 +308,7 @@ final class RemovePackageHelper { mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName); final Computer snapshot = mPm.snapshotComputer(); mPm.mAppsFilter.removePackage(snapshot, - snapshot.getPackageStateInternal(packageName), false /* isReplace */); + snapshot.getPackageStateInternal(packageName)); removedAppId = mPm.mSettings.removePackageLPw(packageName); if (outInfo != null) { outInfo.mRemovedAppId = removedAppId; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 12c9d4b37eed..45c0d6ea6c11 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -455,19 +455,24 @@ public final class Settings implements Watchable, Snappable { // The user's preferred activities associated with particular intent // filters. @Watched - private final WatchedSparseArray<PreferredIntentResolver> - mPreferredActivities = new WatchedSparseArray<>(); + private final WatchedSparseArray<PreferredIntentResolver> mPreferredActivities; + private final SnapshotCache<WatchedSparseArray<PreferredIntentResolver>> + mPreferredActivitiesSnapshot; // The persistent preferred activities of the user's profile/device owner // associated with particular intent filters. @Watched private final WatchedSparseArray<PersistentPreferredIntentResolver> - mPersistentPreferredActivities = new WatchedSparseArray<>(); + mPersistentPreferredActivities; + private final SnapshotCache<WatchedSparseArray<PersistentPreferredIntentResolver>> + mPersistentPreferredActivitiesSnapshot; + // For every user, it is used to find to which other users the intent can be forwarded. @Watched - private final WatchedSparseArray<CrossProfileIntentResolver> - mCrossProfileIntentResolvers = new WatchedSparseArray<>(); + private final WatchedSparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers; + private final SnapshotCache<WatchedSparseArray<CrossProfileIntentResolver>> + mCrossProfileIntentResolversSnapshot; @Watched final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>(); @@ -476,11 +481,12 @@ public final class Settings implements Watchable, Snappable { // For reading/writing settings file. @Watched - private final WatchedArrayList<Signature> mPastSignatures = - new WatchedArrayList<Signature>(); + private final WatchedArrayList<Signature> mPastSignatures; + private final SnapshotCache<WatchedArrayList<Signature>> mPastSignaturesSnapshot; + @Watched - private final WatchedArrayMap<Long, Integer> mKeySetRefs = - new WatchedArrayMap<Long, Integer>(); + private final WatchedArrayMap<Long, Integer> mKeySetRefs; + private final SnapshotCache<WatchedArrayMap<Long, Integer>> mKeySetRefsSnapshot; // Packages that have been renamed since they were first installed. // Keys are the new names of the packages, values are the original @@ -511,7 +517,8 @@ public final class Settings implements Watchable, Snappable { * scanning to make it less confusing. */ @Watched - private final WatchedArrayList<PackageSetting> mPendingPackages = new WatchedArrayList<>(); + private final WatchedArrayList<PackageSetting> mPendingPackages; + private final SnapshotCache<WatchedArrayList<PackageSetting>> mPendingPackagesSnapshot; private final File mSystemDir; @@ -583,6 +590,26 @@ public final class Settings implements Watchable, Snappable { mInstallerPackagesSnapshot = new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages, "Settings.mInstallerPackages"); + mPreferredActivities = new WatchedSparseArray<>(); + mPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(mPreferredActivities, + mPreferredActivities, "Settings.mPreferredActivities"); + mPersistentPreferredActivities = new WatchedSparseArray<>(); + mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Auto<>( + mPersistentPreferredActivities, mPersistentPreferredActivities, + "Settings.mPersistentPreferredActivities"); + mCrossProfileIntentResolvers = new WatchedSparseArray<>(); + mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>( + mCrossProfileIntentResolvers, mCrossProfileIntentResolvers, + "Settings.mCrossProfileIntentResolvers"); + mPastSignatures = new WatchedArrayList<>(); + mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures, + "Settings.mPastSignatures"); + mKeySetRefs = new WatchedArrayMap<>(); + mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs, + "Settings.mKeySetRefs"); + mPendingPackages = new WatchedArrayList<>(); + mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages, + "Settings.mPendingPackages"); mKeySetManagerService = new KeySetManagerService(mPackages); // Test-only handler working on background thread. @@ -623,6 +650,26 @@ public final class Settings implements Watchable, Snappable { mInstallerPackagesSnapshot = new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages, "Settings.mInstallerPackages"); + mPreferredActivities = new WatchedSparseArray<>(); + mPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(mPreferredActivities, + mPreferredActivities, "Settings.mPreferredActivities"); + mPersistentPreferredActivities = new WatchedSparseArray<>(); + mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Auto<>( + mPersistentPreferredActivities, mPersistentPreferredActivities, + "Settings.mPersistentPreferredActivities"); + mCrossProfileIntentResolvers = new WatchedSparseArray<>(); + mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>( + mCrossProfileIntentResolvers, mCrossProfileIntentResolvers, + "Settings.mCrossProfileIntentResolvers"); + mPastSignatures = new WatchedArrayList<>(); + mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures, + "Settings.mPastSignatures"); + mKeySetRefs = new WatchedArrayMap<>(); + mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs, + "Settings.mKeySetRefs"); + mPendingPackages = new WatchedArrayList<>(); + mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages, + "Settings.mPendingPackages"); mKeySetManagerService = new KeySetManagerService(mPackages); mHandler = handler; @@ -699,24 +746,27 @@ public final class Settings implements Watchable, Snappable { mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages); mVersion.putAll(r.mVersion); mVerifierDeviceIdentity = r.mVerifierDeviceIdentity; - WatchedSparseArray.snapshot( - mPreferredActivities, r.mPreferredActivities); - WatchedSparseArray.snapshot( - mPersistentPreferredActivities, r.mPersistentPreferredActivities); - WatchedSparseArray.snapshot( - mCrossProfileIntentResolvers, r.mCrossProfileIntentResolvers); + mPreferredActivities = r.mPreferredActivitiesSnapshot.snapshot(); + mPreferredActivitiesSnapshot = new SnapshotCache.Sealed<>(); + mPersistentPreferredActivities = r.mPersistentPreferredActivitiesSnapshot.snapshot(); + mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Sealed<>(); + mCrossProfileIntentResolvers = r.mCrossProfileIntentResolversSnapshot.snapshot(); + mCrossProfileIntentResolversSnapshot = new SnapshotCache.Sealed<>(); + mSharedUsers.snapshot(r.mSharedUsers); mAppIds = r.mAppIds.snapshot(); - WatchedArrayList.snapshot( - mPastSignatures, r.mPastSignatures); - WatchedArrayMap.snapshot( - mKeySetRefs, r.mKeySetRefs); + + mPastSignatures = r.mPastSignaturesSnapshot.snapshot(); + mPastSignaturesSnapshot = new SnapshotCache.Sealed<>(); + mKeySetRefs = r.mKeySetRefsSnapshot.snapshot(); + mKeySetRefsSnapshot = new SnapshotCache.Sealed<>(); + mRenamedPackages.snapshot(r.mRenamedPackages); mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration); mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp); // mReadMessages - WatchedArrayList.snapshot( - mPendingPackages, r.mPendingPackages); + mPendingPackages = r.mPendingPackagesSnapshot.snapshot(); + mPendingPackagesSnapshot = new SnapshotCache.Sealed<>(); mSystemDir = null; // mKeySetManagerService; mPermissions = r.mPermissions; diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index 0d99075bd614..00f7dc4025e6 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -20,6 +20,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; +import android.content.pm.UserPackage; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -30,7 +31,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.pm.ShortcutService.DumpFilter; -import com.android.server.pm.ShortcutUser.PackageWithUser; import libcore.io.IoUtils; @@ -70,7 +70,7 @@ class ShortcutLauncher extends ShortcutPackageItem { /** * Package name -> IDs. */ - final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); + private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>(); private ShortcutLauncher(@NonNull ShortcutUser shortcutUser, @UserIdInt int ownerUserId, @NonNull String packageName, @@ -101,12 +101,12 @@ class ShortcutLauncher extends ShortcutPackageItem { * Called when the new package can't receive the backup, due to signature or version mismatch. */ private void onRestoreBlocked() { - final ArrayList<PackageWithUser> pinnedPackages = + final ArrayList<UserPackage> pinnedPackages = new ArrayList<>(mPinnedShortcuts.keySet()); mPinnedShortcuts.clear(); for (int i = pinnedPackages.size() - 1; i >= 0; i--) { - final PackageWithUser pu = pinnedPackages.get(i); - final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName); + final UserPackage up = pinnedPackages.get(i); + final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(up.packageName); if (p != null) { p.refreshPinnedFlags(); } @@ -135,13 +135,13 @@ class ShortcutLauncher extends ShortcutPackageItem { return; // No need to instantiate. } - final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName); + final UserPackage up = UserPackage.of(packageUserId, packageName); final int idSize = ids.size(); if (idSize == 0) { - mPinnedShortcuts.remove(pu); + mPinnedShortcuts.remove(up); } else { - final ArraySet<String> prevSet = mPinnedShortcuts.get(pu); + final ArraySet<String> prevSet = mPinnedShortcuts.get(up); // Actually pin shortcuts. // This logic here is to make sure a launcher cannot pin a shortcut that is not dynamic @@ -165,7 +165,7 @@ class ShortcutLauncher extends ShortcutPackageItem { newSet.add(id); } } - mPinnedShortcuts.put(pu, newSet); + mPinnedShortcuts.put(up, newSet); } packageShortcuts.refreshPinnedFlags(); } @@ -176,7 +176,7 @@ class ShortcutLauncher extends ShortcutPackageItem { @Nullable public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName, @UserIdInt int packageUserId) { - return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)); + return mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName)); } /** @@ -207,7 +207,7 @@ class ShortcutLauncher extends ShortcutPackageItem { } boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) { - return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null; + return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null; } public void ensurePackageInfo() { @@ -241,15 +241,15 @@ class ShortcutLauncher extends ShortcutPackageItem { getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup); for (int i = 0; i < size; i++) { - final PackageWithUser pu = mPinnedShortcuts.keyAt(i); + final UserPackage up = mPinnedShortcuts.keyAt(i); - if (forBackup && (pu.userId != getOwnerUserId())) { + if (forBackup && (up.userId != getOwnerUserId())) { continue; // Target package on a different user, skip. (i.e. work profile) } out.startTag(null, TAG_PACKAGE); - ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName); - ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId); + ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, up.packageName); + ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, up.userId); final ArraySet<String> ids = mPinnedShortcuts.valueAt(i); final int idSize = ids.size(); @@ -345,7 +345,7 @@ class ShortcutLauncher extends ShortcutPackageItem { ATTR_PACKAGE_USER_ID, ownerUserId); ids = new ArraySet<>(); ret.mPinnedShortcuts.put( - PackageWithUser.of(packageUserId, packageName), ids); + UserPackage.of(packageUserId, packageName), ids); continue; } } @@ -386,14 +386,14 @@ class ShortcutLauncher extends ShortcutPackageItem { for (int i = 0; i < size; i++) { pw.println(); - final PackageWithUser pu = mPinnedShortcuts.keyAt(i); + final UserPackage up = mPinnedShortcuts.keyAt(i); pw.print(prefix); pw.print(" "); pw.print("Package: "); - pw.print(pu.packageName); + pw.print(up.packageName); pw.print(" User: "); - pw.println(pu.userId); + pw.println(up.userId); final ArraySet<String> ids = mPinnedShortcuts.valueAt(i); final int idSize = ids.size(); @@ -418,7 +418,7 @@ class ShortcutLauncher extends ShortcutPackageItem { @VisibleForTesting ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) { - return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName))); + return new ArraySet<>(mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName))); } @Override diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index b6f09ff4c95a..83720f1445fa 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -57,6 +57,7 @@ import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; +import android.content.pm.UserPackage; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.Bitmap; @@ -118,7 +119,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.pm.ShortcutUser.PackageWithUser; import com.android.server.uri.UriGrantsManagerInternal; import libcore.io.IoUtils; @@ -3774,7 +3774,7 @@ public class ShortcutService extends IShortcutService.Stub { final long start = getStatStartTime(); try { - final ArrayList<PackageWithUser> gonePackages = new ArrayList<>(); + final ArrayList<UserPackage> gonePackages = new ArrayList<>(); synchronized (mLock) { final ShortcutUser user = getUserShortcutsLocked(ownerUserId); @@ -3789,13 +3789,14 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, "Uninstalled: " + spi.getPackageName() + " user " + spi.getPackageUserId()); } - gonePackages.add(PackageWithUser.of(spi)); + gonePackages.add( + UserPackage.of(spi.getPackageUserId(), spi.getPackageName())); } }); if (gonePackages.size() > 0) { for (int i = gonePackages.size() - 1; i >= 0; i--) { - final PackageWithUser pu = gonePackages.get(i); - cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId, + final UserPackage up = gonePackages.get(i); + cleanUpPackageLocked(up.packageName, ownerUserId, up.userId, /* appStillExists = */ false); } } @@ -5274,7 +5275,7 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutUser user = mUsers.get(userId); if (user == null) return null; - return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName)); + return user.getAllLaunchersForTest().get(UserPackage.of(userId, packageName)); } } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 20bbf46f2db5..94eb6bb25caa 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -21,6 +21,7 @@ import android.annotation.UserIdInt; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchSession; import android.content.pm.ShortcutManager; +import android.content.pm.UserPackage; import android.metrics.LogMaker; import android.os.Binder; import android.os.FileUtils; @@ -52,7 +53,6 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -82,44 +82,6 @@ class ShortcutUser { private static final String KEY_LAUNCHERS = "launchers"; private static final String KEY_PACKAGES = "packages"; - static final class PackageWithUser { - final int userId; - final String packageName; - - private PackageWithUser(int userId, String packageName) { - this.userId = userId; - this.packageName = Objects.requireNonNull(packageName); - } - - public static PackageWithUser of(int userId, String packageName) { - return new PackageWithUser(userId, packageName); - } - - public static PackageWithUser of(ShortcutPackageItem spi) { - return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName()); - } - - @Override - public int hashCode() { - return packageName.hashCode() ^ userId; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof PackageWithUser)) { - return false; - } - final PackageWithUser that = (PackageWithUser) obj; - - return userId == that.userId && packageName.equals(that.packageName); - } - - @Override - public String toString() { - return String.format("[Package: %d, %s]", userId, packageName); - } - } - final ShortcutService mService; final AppSearchManager mAppSearchManager; final Executor mExecutor; @@ -129,7 +91,7 @@ class ShortcutUser { private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>(); - private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>(); + private final ArrayMap<UserPackage, ShortcutLauncher> mLaunchers = new ArrayMap<>(); /** In-memory-cached default launcher. */ private String mCachedLauncher; @@ -204,20 +166,20 @@ class ShortcutUser { // We don't expose this directly to non-test code because only ShortcutUser should add to/ // remove from it. @VisibleForTesting - ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() { + ArrayMap<UserPackage, ShortcutLauncher> getAllLaunchersForTest() { return mLaunchers; } private void addLauncher(ShortcutLauncher launcher) { launcher.replaceUser(this); - mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(), + mLaunchers.put(UserPackage.of(launcher.getPackageUserId(), launcher.getPackageName()), launcher); } @Nullable public ShortcutLauncher removeLauncher( @UserIdInt int packageUserId, @NonNull String packageName) { - return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName)); + return mLaunchers.remove(UserPackage.of(packageUserId, packageName)); } @Nullable @@ -242,7 +204,7 @@ class ShortcutUser { @NonNull public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName, @UserIdInt int launcherUserId) { - final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName); + final UserPackage key = UserPackage.of(launcherUserId, packageName); ShortcutLauncher ret = mLaunchers.get(key); if (ret == null) { ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId); diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java index 2cfc8946ad02..e04a1e5b3569 100644 --- a/services/core/java/com/android/server/pm/SnapshotStatistics.java +++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java @@ -24,11 +24,13 @@ import android.os.SystemClock; import android.text.TextUtils; import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.FrameworkStatsLog; import com.android.server.EventLogTags; import java.io.PrintWriter; import java.util.Arrays; import java.util.Locale; +import java.util.concurrent.TimeUnit; /** * This class records statistics about PackageManagerService snapshots. It maintains two sets of @@ -59,9 +61,9 @@ public class SnapshotStatistics { public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000; /** - * The number of ticks for long statistics. This is one week. + * The interval of the snapshot statistics logging. */ - public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60; + private static final long SNAPSHOT_LOG_INTERVAL_US = TimeUnit.DAYS.toMicros(1); /** * The number snapshot event logs that can be generated in a single logging interval. @@ -93,6 +95,28 @@ public class SnapshotStatistics { public static final int SNAPSHOT_SHORT_LIFETIME = 5; /** + * Buckets to represent a range of the rebuild latency for the histogram of + * snapshot rebuild latency. + */ + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_1_MILLIS = 1; + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_2_MILLIS = 2; + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_5_MILLIS = 5; + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_10_MILLIS = 10; + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_20_MILLIS = 20; + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_50_MILLIS = 50; + private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_100_MILLIS = 100; + + /** + * Buckets to represent a range of the reuse count for the histogram of + * snapshot reuse counts. + */ + private static final int REUSE_COUNT_BUCKET_LESS_THAN_1 = 1; + private static final int REUSE_COUNT_BUCKET_LESS_THAN_10 = 10; + private static final int REUSE_COUNT_BUCKET_LESS_THAN_100 = 100; + private static final int REUSE_COUNT_BUCKET_LESS_THAN_1000 = 1000; + private static final int REUSE_COUNT_BUCKET_LESS_THAN_10000 = 10000; + + /** * The lock to control access to this object. */ private final Object mLock = new Object(); @@ -113,11 +137,6 @@ public class SnapshotStatistics { private int mEventsReported = 0; /** - * The tick counter. At the default tick interval, this wraps every 4000 years or so. - */ - private int mTicks = 0; - - /** * The handler used for the periodic ticks. */ private Handler mHandler = null; @@ -139,8 +158,6 @@ public class SnapshotStatistics { // The number of bins private int mCount; - // The mapping of low integers to bins - private int[] mBinMap; // The maximum mapped value. Values at or above this are mapped to the // top bin. private int mMaxBin; @@ -158,16 +175,6 @@ public class SnapshotStatistics { mCount = mUserKey.length + 1; // The maximum value is one more than the last one in the map. mMaxBin = mUserKey[mUserKey.length - 1] + 1; - mBinMap = new int[mMaxBin + 1]; - - int j = 0; - for (int i = 0; i < mUserKey.length; i++) { - while (j <= mUserKey[i]) { - mBinMap[j] = i; - j++; - } - } - mBinMap[mMaxBin] = mUserKey.length; } /** @@ -175,9 +182,14 @@ public class SnapshotStatistics { */ public int getBin(int x) { if (x >= 0 && x < mMaxBin) { - return mBinMap[x]; + for (int i = 0; i < mUserKey.length; i++) { + if (x <= mUserKey[i]) { + return i; + } + } + return 0; // should not happen } else if (x >= mMaxBin) { - return mBinMap[mMaxBin]; + return mUserKey.length; } else { // x is negative. The bin will not be used. return 0; @@ -263,6 +275,11 @@ public class SnapshotStatistics { public int mMaxBuildTimeUs = 0; /** + * The maximum used count since the last log. + */ + public int mMaxUsedCount = 0; + + /** * Record the rebuild. The parameters are the length of time it took to build the * latest snapshot, and the number of times the _previous_ snapshot was used. A * negative value for used signals an invalid value, which is the case the first @@ -279,7 +296,6 @@ public class SnapshotStatistics { } mTotalTimeUs += duration; - boolean reportIt = false; if (big) { mBigBuilds++; @@ -290,6 +306,9 @@ public class SnapshotStatistics { if (mMaxBuildTimeUs < duration) { mMaxBuildTimeUs = duration; } + if (mMaxUsedCount < used) { + mMaxUsedCount = used; + } } private Stats(long now) { @@ -313,6 +332,7 @@ public class SnapshotStatistics { mShortLived = orig.mShortLived; mTotalTimeUs = orig.mTotalTimeUs; mMaxBuildTimeUs = orig.mMaxBuildTimeUs; + mMaxUsedCount = orig.mMaxUsedCount; } /** @@ -443,18 +463,19 @@ public class SnapshotStatistics { } /** - * Report the object via an event. Presumably the record indicates an anomalous - * incident. + * Report the snapshot statistics to FrameworkStatsLog. */ - private void report() { - EventLogTags.writePmSnapshotStats( - mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived, - mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS); + private void logSnapshotStatistics(int packageCount) { + final long avgLatencyUs = (mTotalBuilds == 0 ? 0 : mTotalTimeUs / mTotalBuilds); + final int avgUsedCount = (mTotalBuilds == 0 ? 0 : mTotalUsed / mTotalBuilds); + FrameworkStatsLog.write( + FrameworkStatsLog.PACKAGE_MANAGER_SNAPSHOT_REPORTED, mTimes, mUsed, + mMaxBuildTimeUs, mMaxUsedCount, avgLatencyUs, avgUsedCount, packageCount); } } /** - * Long statistics. These roll over approximately every week. + * Long statistics. These roll over approximately one day. */ private Stats[] mLong; @@ -464,10 +485,14 @@ public class SnapshotStatistics { private Stats[] mShort; /** - * The time of the last build. This can be used to compute the length of time a - * snapshot existed before being replaced. + * The time of last logging to the FrameworkStatsLog. */ - private long mLastBuildTime = 0; + private long mLastLogTimeUs; + + /** + * The number of packages on the device. + */ + private int mPackageCount; /** * Create a snapshot object. Initialize the bin levels. The last bin catches @@ -475,8 +500,20 @@ public class SnapshotStatistics { */ public SnapshotStatistics() { // Create the bin thresholds. The time bins are in units of us. - mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 }); - mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 }); + mTimeBins = new BinMap(new int[] { + REBUILD_LATENCY_BUCKET_LESS_THAN_1_MILLIS, + REBUILD_LATENCY_BUCKET_LESS_THAN_2_MILLIS, + REBUILD_LATENCY_BUCKET_LESS_THAN_5_MILLIS, + REBUILD_LATENCY_BUCKET_LESS_THAN_10_MILLIS, + REBUILD_LATENCY_BUCKET_LESS_THAN_20_MILLIS, + REBUILD_LATENCY_BUCKET_LESS_THAN_50_MILLIS, + REBUILD_LATENCY_BUCKET_LESS_THAN_100_MILLIS }); + mUseBins = new BinMap(new int[] { + REUSE_COUNT_BUCKET_LESS_THAN_1, + REUSE_COUNT_BUCKET_LESS_THAN_10, + REUSE_COUNT_BUCKET_LESS_THAN_100, + REUSE_COUNT_BUCKET_LESS_THAN_1000, + REUSE_COUNT_BUCKET_LESS_THAN_10000 }); // Create the raw statistics final long now = SystemClock.currentTimeMicro(); @@ -484,6 +521,7 @@ public class SnapshotStatistics { mLong[0] = new Stats(now); mShort = new Stats[10]; mShort[0] = new Stats(now); + mLastLogTimeUs = now; // Create the message handler for ticks and start the ticker. mHandler = new Handler(Looper.getMainLooper()) { @@ -516,13 +554,14 @@ public class SnapshotStatistics { * @param now The time at which the snapshot rebuild began, in ns. * @param done The time at which the snapshot rebuild completed, in ns. * @param hits The number of times the previous snapshot was used. + * @param packageCount The number of packages on the device. */ - public final void rebuild(long now, long done, int hits) { + public final void rebuild(long now, long done, int hits, int packageCount) { // The duration has a span of about 2000s final int duration = (int) (done - now); boolean reportEvent = false; synchronized (mLock) { - mLastBuildTime = now; + mPackageCount = packageCount; final int timeBin = mTimeBins.getBin(duration / 1000); final int useBin = mUseBins.getBin(hits); @@ -570,10 +609,12 @@ public class SnapshotStatistics { private void tick() { synchronized (mLock) { long now = SystemClock.currentTimeMicro(); - mTicks++; - if (mTicks % SNAPSHOT_LONG_TICKS == 0) { + if (now - mLastLogTimeUs > SNAPSHOT_LOG_INTERVAL_US) { shift(mLong, now); + mLastLogTimeUs = now; + mLong[mLong.length - 1].logSnapshotStatistics(mPackageCount); } + shift(mShort, now); mEventsReported = 0; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index cf0ea437ea38..f85b11da252e 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.UserManager.DISALLOW_USER_SWITCH; +import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; import android.Manifest; import android.accounts.Account; @@ -52,6 +53,7 @@ import android.content.pm.PackagePartitions; import android.content.pm.ShortcutServiceInternal; import android.content.pm.UserInfo; import android.content.pm.UserInfo.UserInfoFlag; +import android.content.pm.UserPackage; import android.content.pm.UserProperties; import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.res.Configuration; @@ -313,7 +315,7 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting static class UserData { // Basic user information and properties - UserInfo info; + @NonNull UserInfo info; // Account name used when there is a strong association between a user and an account String account; // Account information for seeding into a newly created user. This could also be @@ -3267,11 +3269,39 @@ public class UserManagerService extends IUserManager.Stub { } } + /** Checks whether the device is currently in headless system user mode (for any reason). */ + @Override + public boolean isHeadlessSystemUserMode() { + synchronized (mUsersLock) { + final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM); + return !systemUserData.info.isFull(); + } + } + /** - * Checks whether the device is really headless system user mode, ignoring system user mode - * emulation. + * Checks whether the default state of the device is headless system user mode, i.e. what the + * mode would be if we did a fresh factory reset. + * If the mode is being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be + * returned instead. + * Note that, even in the absence of emulation, a device might deviate from the current default + * due to an OTA changing the default (which won't change the already-decided mode). */ - private boolean isReallyHeadlessSystemUserMode() { + private boolean isDefaultHeadlessSystemUserMode() { + if (!Build.isDebuggable()) { + return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER; + } + + final String emulatedValue = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY); + if (!TextUtils.isEmpty(emulatedValue)) { + if (UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS.equals(emulatedValue)) return true; + if (UserManager.SYSTEM_USER_MODE_EMULATION_FULL.equals(emulatedValue)) return false; + if (!UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT.equals(emulatedValue)) { + Slogf.e(LOG_TAG, "isDefaultHeadlessSystemUserMode(): ignoring invalid valued of " + + "property %s: %s", + SYSTEM_USER_MODE_EMULATION_PROPERTY, emulatedValue); + } + } + return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER; } @@ -3283,30 +3313,11 @@ public class UserManagerService extends IUserManager.Stub { if (!Build.isDebuggable()) { return; } - - final String emulatedValue = SystemProperties - .get(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY); - if (TextUtils.isEmpty(emulatedValue)) { + if (TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) { return; } - final boolean newHeadlessSystemUserMode; - switch (emulatedValue) { - case UserManager.SYSTEM_USER_MODE_EMULATION_FULL: - newHeadlessSystemUserMode = false; - break; - case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS: - newHeadlessSystemUserMode = true; - break; - case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT: - newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode(); - break; - default: - Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of " - + "property %s: %s", UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY, - emulatedValue); - return; - } + final boolean newHeadlessSystemUserMode = isDefaultHeadlessSystemUserMode(); // Update system user type synchronized (mPackagesLock) { @@ -3342,7 +3353,7 @@ public class UserManagerService extends IUserManager.Stub { } } - // Update emulated mode, which will used to triger an update on user packages + // Update emulated mode, which will used to trigger an update on user packages mUpdatingSystemUserMode = true; } @@ -3532,7 +3543,11 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUsersLock) { UserData userData = mUsers.get(UserHandle.USER_SYSTEM); userData.info.flags |= UserInfo.FLAG_SYSTEM; - if (!UserManager.isHeadlessSystemUserMode()) { + // We assume that isDefaultHeadlessSystemUserMode() does not change during the OTA + // from userVersion < 8 since it is documented that pre-R devices do not support its + // modification. Therefore, its current value should be the same as the pre-update + // version. + if (!isDefaultHeadlessSystemUserMode()) { userData.info.flags |= UserInfo.FLAG_FULL; } userIdsToWrite.add(userData.info.id); @@ -3741,7 +3756,7 @@ public class UserManagerService extends IUserManager.Stub { int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY; // Create the system user - String systemUserType = UserManager.isHeadlessSystemUserMode() + String systemUserType = isDefaultHeadlessSystemUserMode() ? UserManager.USER_TYPE_SYSTEM_HEADLESS : UserManager.USER_TYPE_FULL_SYSTEM; flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags(); @@ -5848,6 +5863,7 @@ public class UserManagerService extends IUserManager.Stub { Slog.d(LOG_TAG, "updateUserIds(): userIds= " + Arrays.toString(mUserIds) + " includingPreCreated=" + Arrays.toString(mUserIdsIncludingPreCreated)); } + UserPackage.setValidUserIds(mUserIds); } } @@ -6208,9 +6224,12 @@ public class UserManagerService extends IUserManager.Stub { com.android.internal.R.bool.config_guestUserEphemeral)); pw.println(" Force ephemeral users: " + mForceEphemeralUsers); pw.println(" Is split-system user: " + UserManager.isSplitSystemUser()); - final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode(); + final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode(); pw.println(" Is headless-system mode: " + isHeadlessSystemUserMode); - if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) { + if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) { + pw.println(" (differs from the current default build value)"); + } + if (!TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) { pw.println(" (emulated by 'cmd user set-system-user-mode-emulation')"); if (mUpdatingSystemUserMode) { pw.println(" (and being updated after boot)"); diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index d1f3341e4258..dbd026efed8a 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -147,7 +147,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_WIFI_TETHERING, UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, UserManager.DISALLOW_WIFI_DIRECT, - UserManager.DISALLOW_ADD_WIFI_CONFIG + UserManager.DISALLOW_ADD_WIFI_CONFIG, + UserManager.DISALLOW_CELLULAR_2G }); public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet( @@ -195,7 +196,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CHANGE_WIFI_STATE, UserManager.DISALLOW_WIFI_TETHERING, UserManager.DISALLOW_WIFI_DIRECT, - UserManager.DISALLOW_ADD_WIFI_CONFIG + UserManager.DISALLOW_ADD_WIFI_CONFIG, + UserManager.DISALLOW_CELLULAR_2G ); /** @@ -234,7 +236,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CHANGE_WIFI_STATE, UserManager.DISALLOW_WIFI_TETHERING, UserManager.DISALLOW_WIFI_DIRECT, - UserManager.DISALLOW_ADD_WIFI_CONFIG + UserManager.DISALLOW_ADD_WIFI_CONFIG, + UserManager.DISALLOW_CELLULAR_2G ); /** 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 3b9f0ba06b2c..1fa3b3beeec1 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -406,6 +406,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) @Override public void stopOneTimePermissionSession(String packageName, @UserIdInt int userId) { + super.stopOneTimePermissionSession_enforcePermission(); + Objects.requireNonNull(packageName); final long token = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java index 5f76fbc0ec36..79e35c2daf6f 100644 --- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java @@ -384,7 +384,7 @@ public class CpuWakeupStats { private static final class Wakeup { private static final String PARSER_TAG = "CpuWakeupStats.Wakeup"; private static final String ABORT_REASON_PREFIX = "Abort"; - private static final Pattern sIrqPattern = Pattern.compile("(\\d+)\\s+(\\S+)"); + private static final Pattern sIrqPattern = Pattern.compile("^(\\d+)\\s+(\\S+)"); String mRawReason; long mElapsedMillis; @@ -409,7 +409,7 @@ public class CpuWakeupStats { IrqDevice[] parsedDevices = new IrqDevice[components.length]; for (String component : components) { - final Matcher matcher = sIrqPattern.matcher(component); + final Matcher matcher = sIrqPattern.matcher(component.trim()); if (matcher.find()) { final int line; final String device; diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 4111446ae670..5b341cede47d 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -18,7 +18,7 @@ package com.android.server.statusbar; import android.annotation.Nullable; import android.app.ITransientNotificationCallback; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.Bundle; import android.os.IBinder; import android.view.InsetsState.InternalInsetsType; @@ -192,9 +192,10 @@ public interface StatusBarManagerInternal { void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable); /** - * Sets the system-wide listener for UDFPS HBM status changes. + * Sets the system-wide callback for UDFPS refresh rate changes. * - * @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener) + * @see com.android.internal.statusbar.IStatusBar#setUdfpsRefreshRateCallback + * (IUdfpsRefreshRateRequestCallback) */ - void setUdfpsHbmListener(IUdfpsHbmListener listener); + void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index d378b11f02bf..c02ebfd3b196 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -16,12 +16,15 @@ package com.android.server.statusbar; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; import static android.app.StatusBarManager.NAV_BAR_MODE_DEFAULT; import static android.app.StatusBarManager.NAV_BAR_MODE_KIDS; import static android.app.StatusBarManager.NavBarMode; import static android.app.StatusBarManager.SessionFlags; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; @@ -53,7 +56,7 @@ import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.net.Uri; @@ -172,7 +175,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private final SparseArray<UiState> mDisplayUiState = new SparseArray<>(); @GuardedBy("mLock") - private IUdfpsHbmListener mUdfpsHbmListener; + private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback; @GuardedBy("mLock") private IBiometricContextListener mBiometricContextListener; @@ -691,13 +694,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setUdfpsHbmListener(IUdfpsHbmListener listener) { + public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { synchronized (mLock) { - mUdfpsHbmListener = listener; + mUdfpsRefreshRateRequestCallback = callback; } if (mBar != null) { try { - mBar.setUdfpsHbmListener(listener); + mBar.setUdfpsRefreshRateCallback(callback); } catch (RemoteException ex) { } } } @@ -941,11 +944,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setUdfpsHbmListener(IUdfpsHbmListener listener) { + public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) { enforceStatusBarService(); if (mBar != null) { try { - mBar.setUdfpsHbmListener(listener); + mBar.setUdfpsRefreshRateCallback(callback); } catch (RemoteException ex) { } } @@ -1304,18 +1307,23 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D "StatusBarManagerService"); } + private boolean doesCallerHoldInteractAcrossUserPermission() { + return mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED + || mContext.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED; + } + /** * For targetSdk S+ we require STATUS_BAR. For targetSdk < S, we only require EXPAND_STATUS_BAR * but also require that it falls into one of the allowed use-cases to lock down abuse vector. */ private boolean checkCanCollapseStatusBar(String method) { int uid = Binder.getCallingUid(); - int pid = Binder.getCallingUid(); + int pid = Binder.getCallingPid(); if (CompatChanges.isChangeEnabled(LOCK_DOWN_COLLAPSE_STATUS_BAR, uid)) { enforceStatusBar(); } else { if (mContext.checkPermission(Manifest.permission.STATUS_BAR, pid, uid) - != PackageManager.PERMISSION_GRANTED) { + != PERMISSION_GRANTED) { enforceExpandStatusBar(); if (!mActivityTaskManager.canCloseSystemDialogs(pid, uid)) { Slog.e(TAG, "Permission Denial: Method " + method + "() requires permission " @@ -1366,11 +1374,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null); }); // If StatusBarService dies, system_server doesn't get killed with it, so we need to make - // sure the UDFPS listener is refreshed as well. Deferring to the handler just so to avoid + // sure the UDFPS callback is refreshed as well. Deferring to the handler just so to avoid // making registerStatusBar re-entrant. mHandler.post(() -> { synchronized (mLock) { - setUdfpsHbmListener(mUdfpsHbmListener); + setUdfpsRefreshRateCallback(mUdfpsRefreshRateRequestCallback); setBiometicContextListener(mBiometricContextListener); } }); @@ -2021,6 +2029,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } final int userId = mCurrentUserId; + final int callingUserId = UserHandle.getUserId(Binder.getCallingUid()); + if (mCurrentUserId != callingUserId && !doesCallerHoldInteractAcrossUserPermission()) { + throw new SecurityException("Calling user id: " + callingUserId + + ", cannot call on behalf of current user id: " + mCurrentUserId + "."); + } final long userIdentity = Binder.clearCallingIdentity(); try { Settings.Secure.putIntForUser(mContext.getContentResolver(), diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index cd0096bd9e42..c1920576ad36 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1752,6 +1752,8 @@ public class TrustManagerService extends SystemService { @android.annotation.EnforcePermission(android.Manifest.permission.TRUST_LISTENER) @Override public boolean isTrustUsuallyManaged(int userId) { + super.isTrustUsuallyManaged_enforcePermission(); + return isTrustUsuallyManagedInternal(userId); } diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java index 11766a3d70bd..770cb7258319 100644 --- a/services/core/java/com/android/server/utils/EventLogger.java +++ b/services/core/java/com/android/server/utils/EventLogger.java @@ -26,7 +26,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Locale; /** @@ -79,20 +81,34 @@ public class EventLogger { enqueue(event.printLog(logType, tag)); } + /** Dumps events into the given {@link DumpSink}. */ + public synchronized void dump(DumpSink dumpSink) { + dumpSink.sink(mTag, new ArrayList<>(mEvents)); + } + /** Dumps events using {@link PrintWriter}. */ public synchronized void dump(PrintWriter pw) { dump(pw, "" /* prefix */); } /** Dumps events using {@link PrintWriter} with a certain indent. */ - public synchronized void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "Events log: " + mTag); - String indent = prefix + " "; + public synchronized void dump(PrintWriter pw, String indent) { + pw.println(indent + "Events log: " + mTag); + + String childrenIndention = indent + " "; for (Event evt : mEvents) { - pw.println(indent + evt.toString()); + pw.println(childrenIndention + evt.toString()); } } + /** Receives events from {@link EventLogger} upon a {@link #dump(DumpSink)} call. **/ + public interface DumpSink { + + /** Processes given events into some pipeline with a given tag. **/ + void sink(String tag, List<Event> events); + + } + public abstract static class Event { /** Timestamps formatter. */ diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index abb57bca3704..7dc4f9782e70 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -320,9 +320,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub IRemoteCallback.Stub callback = new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { - if (DEBUG) { - Slog.d(TAG, "publish system wallpaper changed!"); - } + Slog.d(TAG, "publish system wallpaper changed!"); notifyWallpaperChanged(wallpaper); } }; @@ -1551,6 +1549,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { mReply.sendResult(null); } catch (RemoteException e) { + Slog.d(TAG, "failed to send callback!", e); + } finally { Binder.restoreCallingIdentity(ident); } mReply = null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index ecc43f7b938b..b153a85a4048 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1316,7 +1316,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mAppSwitchesState = APP_SWITCH_ALLOW; } } - return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null, + return pir.sendInner(caller, 0, fillInIntent, resolvedType, allowlistToken, null, null, resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions); } diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java index d42a74f60de9..7c0d6586701f 100644 --- a/services/core/java/com/android/server/wm/AnrController.java +++ b/services/core/java/com/android/server/wm/AnrController.java @@ -40,6 +40,7 @@ import com.android.server.criticalevents.CriticalEventLog; import java.io.File; import java.util.ArrayList; import java.util.OptionalInt; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -332,9 +333,10 @@ class AnrController { String criticalEvents = CriticalEventLog.getInstance().logLinesForSystemServerTraceFile(); final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids, - null /* processCpuTracker */, null /* lastPids */, nativePids, + null /* processCpuTracker */, null /* lastPids */, + CompletableFuture.completedFuture(nativePids), null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents, - null/* AnrLatencyTracker */); + Runnable::run, null/* AnrLatencyTracker */); if (tracesFile != null) { tracesFile.renameTo( new File(tracesFile.getParent(), tracesFile.getName() + "_pre")); diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index e7ab63eab202..13a1cb6daf38 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -216,14 +216,10 @@ class Dimmer { return; } - if (container != null) { - // The dim method is called from WindowState.prepareSurfaces(), which is always called - // in the correct Z from lowest Z to highest. This ensures that the dim layer is always - // relative to the highest Z layer with a dim. - t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); - } else { - t.setLayer(d.mDimLayer, Integer.MAX_VALUE); - } + // The dim method is called from WindowState.prepareSurfaces(), which is always called + // in the correct Z from lowest Z to highest. This ensures that the dim layer is always + // relative to the highest Z layer with a dim. + t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer); t.setAlpha(d.mDimLayer, alpha); t.setBackgroundBlurRadius(d.mDimLayer, blurRadius); @@ -231,32 +227,6 @@ class Dimmer { } /** - * Finish a dim started by dimAbove in the case there was no call to dimAbove. - * - * @param t A Transaction in which to finish the dim. - */ - void stopDim(SurfaceControl.Transaction t) { - if (mDimState != null) { - t.hide(mDimState.mDimLayer); - mDimState.isVisible = false; - mDimState.mDontReset = false; - } - } - - /** - * Place a Dim above the entire host container. The caller is responsible for calling stopDim to - * remove this effect. If the Dim can be assosciated with a particular child of the host - * consider using the other variant of dimAbove which ties the Dim lifetime to the child - * lifetime more explicitly. - * - * @param t A transaction in which to apply the Dim. - * @param alpha The alpha at which to Dim. - */ - void dimAbove(SurfaceControl.Transaction t, float alpha) { - dim(t, null, 1, alpha, 0); - } - - /** * Place a dim above the given container, which should be a child of the host container. * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset * and the child should call dimAbove again to request the Dim to continue. diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 87239944fa60..38931f22b312 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -75,11 +75,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; -import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -1341,90 +1337,6 @@ public class DisplayPolicy { */ int selectAnimation(WindowState win, int transit) { ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit); - if (win == mStatusBar) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - return R.anim.dock_top_exit; - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_top_enter; - } - } else if (win == mNavigationBar) { - if (win.getAttrs().windowAnimations != 0) { - return ANIMATION_STYLEABLE; - } - // This can be on either the bottom or the right or the left. - if (mNavigationBarPosition == NAV_BAR_BOTTOM) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) { - return R.anim.dock_bottom_exit_keyguard; - } else { - return R.anim.dock_bottom_exit; - } - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_bottom_enter; - } - } else if (mNavigationBarPosition == NAV_BAR_RIGHT) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - return R.anim.dock_right_exit; - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_right_enter; - } - } else if (mNavigationBarPosition == NAV_BAR_LEFT) { - if (transit == TRANSIT_EXIT - || transit == TRANSIT_HIDE) { - return R.anim.dock_left_exit; - } else if (transit == TRANSIT_ENTER - || transit == TRANSIT_SHOW) { - return R.anim.dock_left_enter; - } - } - } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt - || win == mExtraNavBarAlt) { - if (win.getAttrs().windowAnimations != 0) { - return ANIMATION_STYLEABLE; - } - - int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition; - - boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE; - boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW; - - switch (pos) { - case ALT_BAR_LEFT: - if (isExitOrHide) { - return R.anim.dock_left_exit; - } else if (isEnterOrShow) { - return R.anim.dock_left_enter; - } - break; - case ALT_BAR_RIGHT: - if (isExitOrHide) { - return R.anim.dock_right_exit; - } else if (isEnterOrShow) { - return R.anim.dock_right_enter; - } - break; - case ALT_BAR_BOTTOM: - if (isExitOrHide) { - return R.anim.dock_bottom_exit; - } else if (isEnterOrShow) { - return R.anim.dock_bottom_enter; - } - break; - case ALT_BAR_TOP: - if (isExitOrHide) { - return R.anim.dock_top_exit; - } else if (isEnterOrShow) { - return R.anim.dock_top_enter; - } - break; - } - } if (transit == TRANSIT_PREVIEW_DONE) { if (win.hasAppShownWindows()) { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index a8d13c57ffa2..eaa08fd5eb0b 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1578,7 +1578,9 @@ public class DisplayRotation { false /* forceRelayout */); } else { // Revert the rotation to our saved value if we transition from HALF_FOLDED. - mRotation = mHalfFoldSavedRotation; + 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). mService.updateRotation(false /* alwaysSendConfiguration */, diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index b9fa80cf2c0f..7e56dbf5602b 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -26,7 +26,6 @@ import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; -import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -289,9 +288,8 @@ class InsetsPolicy { final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); // Always use windowing mode fullscreen when get insets for window metrics to make sure it // contains all insets types. - final InsetsState originalState = mDisplayContent.getInsetsPolicy() - .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop, - attrs.type, mStateController.getRawInsetsState()); + final InsetsState originalState = enforceInsetsPolicyForTarget(WINDOWING_MODE_FULLSCREEN, + alwaysOnTop, attrs, mStateController.getRawInsetsState()); InsetsState state = adjustVisibilityForTransientTypes(originalState); return adjustInsetsForRoundedCorners(token, state, state == originalState); } @@ -343,56 +341,42 @@ class InsetsPolicy { /** - * Modifies the given {@code state} according to the {@code type} (Inset type) provided by - * the target. - * When performing layout of the target or dispatching insets to the target, we need to exclude - * sources which should not be visible to the target. e.g., the source which represents the - * target window itself, and the IME source when the target is above IME. We also need to - * exclude certain types of insets source for client within specific windowing modes. + * Modifies the given {@code state} according to the target's window state. + * When performing layout of the target or dispatching insets to the target, we need to adjust + * sources based on the target. e.g., the floating window will not receive system bars other + * than caption, and some insets provider may request to override sizes for given window types. + * Since the window type and the insets types provided by the window shall not change at + * runtime, rotation doesn't matter in the layout params. * - * @param type the inset type provided by the target * @param windowingMode the windowing mode of the target * @param isAlwaysOnTop is the target always on top - * @param windowType the type of the target + * @param attrs the layout params of the target * @param state the input inset state containing all the sources * @return The state stripped of the necessary information. */ - InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type, - @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop, - int windowType, InsetsState state) { + InsetsState enforceInsetsPolicyForTarget(@WindowConfiguration.WindowingMode int windowingMode, + boolean isAlwaysOnTop, WindowManager.LayoutParams attrs, InsetsState state) { boolean stateCopied = false; - if (type != ITYPE_INVALID) { + if (attrs.providedInsets != null && attrs.providedInsets.length > 0) { state = new InsetsState(state); stateCopied = true; - state.removeSource(type); - - // Navigation bar doesn't get influenced by anything else - if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) { - state.removeSource(ITYPE_STATUS_BAR); - state.removeSource(ITYPE_CLIMATE_BAR); - state.removeSource(ITYPE_CAPTION_BAR); - state.removeSource(ITYPE_NAVIGATION_BAR); - state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR); - } - - // Status bar doesn't get influenced by caption bar - if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) { - state.removeSource(ITYPE_CAPTION_BAR); + for (int i = attrs.providedInsets.length - 1; i >= 0; i--) { + state.removeSource(attrs.providedInsets[i].type); } } ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController .getSourceProviders(); for (int i = providers.size() - 1; i >= 0; i--) { WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i); - if (otherProvider.overridesFrame(windowType)) { + if (otherProvider.overridesFrame(attrs.type)) { if (!stateCopied) { state = new InsetsState(state); stateCopied = true; } InsetsSource override = new InsetsSource(state.getSource(otherProvider.getSource().getType())); - override.setFrame(otherProvider.getOverriddenFrame(windowType)); + override.setFrame(otherProvider.getOverriddenFrame(attrs.type)); state.addSource(override); } } diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java index f11c2a7da840..dcb7fe3fbc8b 100644 --- a/services/core/java/com/android/server/wm/LockTaskController.java +++ b/services/core/java/com/android/server/wm/LockTaskController.java @@ -604,7 +604,10 @@ public class LockTaskController { getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId); } if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) { - getStatusBarService().showPinningEnterExitToast(false /* entering */); + final IStatusBarService statusBarService = getStatusBarService(); + if (statusBarService != null) { + statusBarService.showPinningEnterExitToast(false /* entering */); + } } mWindowManager.onLockTaskStateChanged(mLockTaskModeState); } catch (RemoteException ex) { @@ -619,7 +622,10 @@ public class LockTaskController { void showLockTaskToast() { if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) { try { - getStatusBarService().showPinningEscapeToast(); + final IStatusBarService statusBarService = getStatusBarService(); + if (statusBarService != null) { + statusBarService.showPinningEscapeToast(); + } } catch (RemoteException e) { Slog.e(TAG, "Failed to send pinning escape toast", e); } @@ -727,7 +733,10 @@ public class LockTaskController { // When lock task starts, we disable the status bars. try { if (lockTaskModeState == LOCK_TASK_MODE_PINNED) { - getStatusBarService().showPinningEnterExitToast(true /* entering */); + final IStatusBarService statusBarService = getStatusBarService(); + if (statusBarService != null) { + statusBarService.showPinningEnterExitToast(true /* entering */); + } } mWindowManager.onLockTaskStateChanged(lockTaskModeState); mLockTaskModeState = lockTaskModeState; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 80c980310664..a64bd694605c 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -469,6 +469,48 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe } /** + * Records that a particular container has been reparented. This only effects windows that have + * already been collected in the transition. This should be called before reparenting because + * the old parent may be removed during reparenting, for example: + * {@link Task#shouldRemoveSelfOnLastChildRemoval} + */ + void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) { + if (!mChanges.containsKey(wc)) { + // #collectReparentChange() will be called when the window is reparented. Skip if it is + // a window that has not been collected, which means we don't care about this window for + // the current transition. + return; + } + final ChangeInfo change = mChanges.get(wc); + // Use the current common ancestor if there are multiple reparent, and the original parent + // has been detached. Otherwise, use the original parent before the transition. + final WindowContainer prevParent = + change.mStartParent == null || change.mStartParent.isAttached() + ? change.mStartParent + : change.mCommonAncestor; + if (prevParent == null || !prevParent.isAttached()) { + Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has" + + " been detached: " + wc); + return; + } + if (prevParent == newParent) { + Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: " + + wc); + return; + } + if (!newParent.isAttached()) { + Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after" + + " reparenting: " + wc); + return; + } + WindowContainer ancestor = newParent; + while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { + ancestor = ancestor.getParent(); + } + change.mCommonAncestor = ancestor; + } + + /** * @return {@code true} if `wc` is a participant or is a descendant of one. */ boolean isInTransition(WindowContainer wc) { @@ -830,8 +872,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe void abort() { // This calls back into itself via controller.abort, so just early return here. if (mState == STATE_ABORT) return; - if (mState != STATE_COLLECTING) { - throw new IllegalStateException("Too late to abort."); + if (mState != STATE_COLLECTING && mState != STATE_STARTED) { + throw new IllegalStateException("Too late to abort. state=" + mState); } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); mState = STATE_ABORT; @@ -1524,20 +1566,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return out; } - // Find the top-most shared ancestor of app targets. - WindowContainer<?> ancestor = topApp.getParent(); - // Go up ancestor parent chain until all targets are descendants. - ancestorLoop: - while (ancestor != null) { - for (int i = sortedTargets.size() - 1; i >= 0; --i) { - final WindowContainer wc = sortedTargets.get(i); - if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) { - ancestor = ancestor.getParent(); - continue ancestorLoop; - } - } - break; - } + 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); @@ -1654,6 +1683,46 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe return out; } + /** + * Finds the top-most common ancestor of app targets. + * + * Makes sure that the previous parent is also a descendant to make sure the animation won't + * be covered by other windows below the previous parent. For example, when reparenting an + * activity from PiP Task to split screen Task. + */ + @NonNull + private static WindowContainer<?> findCommonAncestor( + @NonNull ArrayList<WindowContainer> targets, + @NonNull ArrayMap<WindowContainer, ChangeInfo> changes, + @NonNull WindowContainer<?> topApp) { + WindowContainer<?> ancestor = topApp.getParent(); + // Go up ancestor parent chain until all targets are descendants. Ancestor should never be + // null because all targets are attached. + for (int i = targets.size() - 1; i >= 0; i--) { + final WindowContainer wc = targets.get(i); + if (isWallpaper(wc)) { + // Skip the non-app window. + continue; + } + while (!wc.isDescendantOf(ancestor)) { + ancestor = ancestor.getParent(); + } + + // Make sure the previous parent is also a descendant to make sure the animation won't + // be covered by other windows below the previous parent. For example, when reparenting + // an activity from PiP Task to split screen Task. + final ChangeInfo change = changes.get(wc); + final WindowContainer prevParent = change.mCommonAncestor; + if (prevParent == null || !prevParent.isAttached()) { + continue; + } + while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) { + ancestor = ancestor.getParent(); + } + } + return ancestor; + } + private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type, ArrayList<WindowContainer> sortedTargets) { // Find the layout params of the top-most application window that is part of the @@ -1772,10 +1841,19 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe @Retention(RetentionPolicy.SOURCE) @interface Flag {} - // Usually "post" change state. + /** + * "Parent" that is also included in the transition. When populating the parent changes, we + * may skip the intermediate parents, so this may not be the actual parent in the hierarchy. + */ WindowContainer mEndParent; - // Parent before change state. + /** Actual parent window before change state. */ WindowContainer mStartParent; + /** + * When the window is reparented during the transition, this is the common ancestor window + * of the {@link #mStartParent} and the current parent. This is needed because the + * {@link #mStartParent} may have been detached when the transition starts. + */ + WindowContainer mCommonAncestor; // State tracking boolean mExistenceChanged = false; diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index ac85c9a36bcc..37bef3a833ee 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -533,6 +533,17 @@ class TransitionController { mCollectingTransition.collectVisibleChange(wc); } + /** + * Records that a particular container has been reparented. This only effects windows that have + * already been collected in the transition. This should be called before reparenting because + * the old parent may be removed during reparenting, for example: + * {@link Task#shouldRemoveSelfOnLastChildRemoval} + */ + void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) { + if (!isCollecting()) return; + mCollectingTransition.collectReparentChange(wc, newParent); + } + /** @see Transition#mStatusBarTransitionDelay */ void setStatusBarTransitionDelay(long delay) { if (mCollectingTransition == null) return; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 0b5de85c5cab..73d4496bdeb5 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -542,6 +542,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< throw new IllegalArgumentException("WC=" + this + " already child of " + mParent); } + // Collect before removing child from old parent, because the old parent may be removed if + // this is the last child in it. + mTransitionController.collectReparentChange(this, newParent); + // The display object before reparenting as that might lead to old parent getting removed // from the display if it no longer has any child. final DisplayContent prevDc = oldParent.getDisplayContent(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6d750a469f8c..f30c4355306c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -29,7 +29,6 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.os.PowerManager.DRAW_WAKE_LOCK; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.InsetsState.ITYPE_IME; -import static android.view.InsetsState.ITYPE_INVALID; import static android.view.SurfaceControl.Transaction; import static android.view.SurfaceControl.getGlobalTransaction; import static android.view.ViewRootImpl.LOCAL_LAYOUT; @@ -1673,14 +1672,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (rotatedState != null) { return insetsPolicy.adjustInsetsForWindow(this, rotatedState); } - final InsetsSourceProvider provider = getControllableInsetProvider(); - final @InternalInsetsType int insetTypeProvidedByWindow = provider != null - ? provider.getSource().getType() : ITYPE_INVALID; final InsetsState rawInsetsState = mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState(); final InsetsState insetsStateForWindow = insetsPolicy - .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow, - getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState); + .enforceInsetsPolicyForTarget( + getWindowingMode(), isAlwaysOnTop(), mAttrs, rawInsetsState); return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow, includeTransient); } @@ -5713,6 +5709,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return super.getAnimationLeashParent(); } + @Override + public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) { + super.onAnimationLeashCreated(t, leash); + if (isStartingWindowAssociatedToTask()) { + // Make sure the animation leash is still on top of the task. + t.setLayer(leash, Integer.MAX_VALUE); + } + } + // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA // then we can drop all negative layering on the windowing side and simply inherit // the default implementation here. diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 5c0557f2d1f4..6e16b5de1d85 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -602,11 +602,17 @@ class WindowStateAnimator { return true; } - final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD; - if (isEntrance && isImeWindow) { + if (mWin.mAttrs.type == TYPE_INPUT_METHOD) { mWin.getDisplayContent().adjustForImeIfNeeded(); - mWin.setDisplayLayoutNeeded(); - mService.mWindowPlacerLocked.requestTraversal(); + if (isEntrance) { + mWin.setDisplayLayoutNeeded(); + mService.mWindowPlacerLocked.requestTraversal(); + } + } + + if (mWin.mControllableInsetProvider != null) { + // All our animations should be driven by the insets control target. + return false; } // Only apply an animation if the display isn't frozen. If it is @@ -654,14 +660,10 @@ class WindowStateAnimator { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); mAnimationIsEntrance = isEntrance; } - } else if (!isImeWindow) { + } else { mWin.cancelAnimation(); } - if (!isEntrance && isImeWindow) { - mWin.getDisplayContent().adjustForImeIfNeeded(); - } - return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION); } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 69fb1eaa7543..dcf094f99aae 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -16,7 +16,6 @@ package com.android.server.credentials; import android.annotation.NonNull; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.credentials.ui.IntentFactory; @@ -48,7 +47,7 @@ public class CredentialManagerUi { }; private void handleUiResult(int resultCode, Bundle resultData) { - if (resultCode == Activity.RESULT_OK) { + if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION) { UserSelectionDialogResult selection = UserSelectionDialogResult .fromResultData(resultData); if (selection != null) { @@ -56,7 +55,7 @@ public class CredentialManagerUi { } else { Slog.i(TAG, "No selection found in UI result"); } - } else if (resultCode == Activity.RESULT_CANCELED) { + } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) { mCallbacks.onUiCancelation(); } } @@ -84,8 +83,9 @@ public class CredentialManagerUi { */ public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) { Log.i(TAG, "In show"); - Intent intent = IntentFactory.newIntent(requestInfo, providerDataList, - mResultReceiver); + Intent intent = IntentFactory.newIntent( + requestInfo, providerDataList, + new ArrayList<>(), mResultReceiver); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 24610df7ad79..ff2107a95d25 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.slice.Slice; import android.credentials.ui.Entry; -import android.credentials.ui.ProviderData; +import android.credentials.ui.GetCredentialProviderData; import android.service.credentials.Action; import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderInfo; @@ -117,7 +117,7 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsResp } @Override - protected final ProviderData prepareUiData() throws IllegalArgumentException { + protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException { Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isCompletionStatus(getStatus())) { Log.i(TAG, "In prepareUiData not complete"); @@ -147,7 +147,7 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsResp * To be called by {@link ProviderGetSession} when the UI is to be invoked. */ @Nullable - private ProviderData prepareUiProviderDataWithCredentials(@NonNull + private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull CredentialsDisplayContent content) { Log.i(TAG, "in prepareUiProviderData"); List<Entry> credentialEntries = new ArrayList<>(); @@ -173,15 +173,10 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsResp action.getSlice())); } - // TODO : Set the correct last used time - return new ProviderData.Builder(mComponentName.flattenToString(), - mProviderInfo.getServiceLabel() == null ? "" : - mProviderInfo.getServiceLabel().toString(), - /*icon=*/null) + return new GetCredentialProviderData.Builder(mComponentName.flattenToString()) .setCredentialEntries(credentialEntries) .setActionChips(actionChips) .setAuthenticationEntry(authenticationEntry) - .setLastUsedTimeMillis(0) .build(); } @@ -189,7 +184,7 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsResp * To be called by {@link ProviderGetSession} when the UI is to be invoked. */ @Nullable - private ProviderData prepareUiProviderDataWithAuthentication(@NonNull + private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull Action authenticationEntry) { // TODO : Implement authentication flow return null; diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp new file mode 100644 index 000000000000..939fb6a9d170 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/Android.bp @@ -0,0 +1,62 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "FrameworksInputMethodSystemServerTests", + defaults: [ + "modules-utils-testable-device-config-defaults", + ], + + srcs: [ + "src/**/*.java", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "androidx.test.espresso.core", + "androidx.test.espresso.contrib", + "androidx.test.ext.truth", + "frameworks-base-testutils", + "mockito-target-extended-minus-junit4", + "platform-test-annotations", + "services.core", + "servicestests-core-utils", + "servicestests-utils-mockito-extended", + "truth-prebuilt", + ], + + libs: [ + "android.test.mock", + "android.test.base", + "android.test.runner", + ], + + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + + optimize: { + enabled: false, + }, +} diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml new file mode 100644 index 000000000000..12e7cfc28a56 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.inputmethodtests"> + + <uses-sdk android:targetSdkVersion="31" /> + + <!-- Permissions required for granting and logging --> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/> + <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/> + <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> + + <!-- Permissions for reading system info --> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + + <application android:testOnly="true" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.inputmethodtests" + android:label="Frameworks InputMethod System Service Tests" /> + +</manifest> diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml new file mode 100644 index 000000000000..92be78060da8 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs Frameworks InputMethod System Services Tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="FrameworksInputMethodSystemServerTests.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksInputMethodSystemServerTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.inputmethodtests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> + + <!-- Collect the files in the dump directory for debugging --> + <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> + <option name="directory-keys" value="/sdcard/FrameworksInputMethodSystemServerTests/" /> + <option name="collect-on-run-ended-only" value="true" /> + </metrics_collector> +</configuration> diff --git a/services/tests/InputMethodSystemServerTests/OWNERS b/services/tests/InputMethodSystemServerTests/OWNERS new file mode 100644 index 000000000000..1f2c036773a4 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/inputmethod/OWNERS diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java new file mode 100644 index 000000000000..3fbc4004785d --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -0,0 +1,259 @@ +/* + * 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.inputmethod; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +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.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManagerInternal; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManager; +import android.os.Binder; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.view.inputmethod.EditorInfo; +import android.window.ImeOnBackInvokedDispatcher; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.compat.IPlatformCompat; +import com.android.internal.inputmethod.IInputMethod; +import com.android.internal.inputmethod.IInputMethodClient; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.inputmethod.IRemoteInputConnection; +import com.android.internal.inputmethod.InputBindResult; +import com.android.internal.view.IInputMethodManager; +import com.android.server.LocalServices; +import com.android.server.ServiceThread; +import com.android.server.SystemServerInitThreadPool; +import com.android.server.SystemService; +import com.android.server.input.InputManagerInternal; +import com.android.server.pm.UserManagerInternal; +import com.android.server.wm.WindowManagerInternal; + +import org.junit.After; +import org.junit.Before; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; + +/** Base class for testing {@link InputMethodManagerService}. */ +public class InputMethodManagerServiceTestBase { + protected static final String TEST_SELECTED_IME_ID = "test.ime"; + protected static final String TEST_EDITOR_PKG_NAME = "test.editor"; + protected static final String TEST_FOCUSED_WINDOW_NAME = "test.editor/activity"; + protected static final WindowManagerInternal.ImeTargetInfo TEST_IME_TARGET_INFO = + new WindowManagerInternal.ImeTargetInfo( + TEST_FOCUSED_WINDOW_NAME, + TEST_FOCUSED_WINDOW_NAME, + TEST_FOCUSED_WINDOW_NAME, + TEST_FOCUSED_WINDOW_NAME, + TEST_FOCUSED_WINDOW_NAME); + protected static final InputBindResult SUCCESS_WAITING_IME_BINDING_RESULT = + new InputBindResult( + InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, + null, + null, + null, + "0", + 0, + null, + false); + + @Mock protected WindowManagerInternal mMockWindowManagerInternal; + @Mock protected ActivityManagerInternal mMockActivityManagerInternal; + @Mock protected PackageManagerInternal mMockPackageManagerInternal; + @Mock protected InputManagerInternal mMockInputManagerInternal; + @Mock protected DisplayManagerInternal mMockDisplayManagerInternal; + @Mock protected UserManagerInternal mMockUserManagerInternal; + @Mock protected InputMethodBindingController mMockInputMethodBindingController; + @Mock protected IInputMethodClient mMockInputMethodClient; + @Mock protected IBinder mWindowToken; + @Mock protected IRemoteInputConnection mMockRemoteInputConnection; + @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection; + @Mock protected ImeOnBackInvokedDispatcher mMockImeOnBackInvokedDispatcher; + @Mock protected IInputMethodManager.Stub mMockIInputMethodManager; + @Mock protected IPlatformCompat.Stub mMockIPlatformCompat; + @Mock protected IInputMethod mMockInputMethod; + @Mock protected IBinder mMockInputMethodBinder; + @Mock protected IInputManager mMockIInputManager; + + protected Context mContext; + protected MockitoSession mMockingSession; + protected int mTargetSdkVersion; + protected int mCallingUserId; + protected EditorInfo mEditorInfo; + protected IInputMethodInvoker mMockInputMethodInvoker; + protected InputMethodManagerService mInputMethodManagerService; + protected ServiceThread mServiceThread; + + @Before + public void setUp() throws RemoteException { + mMockingSession = + mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .mockStatic(LocalServices.class) + .mockStatic(ServiceManager.class) + .mockStatic(SystemServerInitThreadPool.class) + .startMocking(); + + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + spyOn(mContext); + + mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; + mCallingUserId = UserHandle.getCallingUserId(); + mEditorInfo = new EditorInfo(); + mEditorInfo.packageName = TEST_EDITOR_PKG_NAME; + + // Injecting and mocking local services. + doReturn(mMockWindowManagerInternal) + .when(() -> LocalServices.getService(WindowManagerInternal.class)); + doReturn(mMockActivityManagerInternal) + .when(() -> LocalServices.getService(ActivityManagerInternal.class)); + doReturn(mMockPackageManagerInternal) + .when(() -> LocalServices.getService(PackageManagerInternal.class)); + doReturn(mMockInputManagerInternal) + .when(() -> LocalServices.getService(InputManagerInternal.class)); + doReturn(mMockDisplayManagerInternal) + .when(() -> LocalServices.getService(DisplayManagerInternal.class)); + doReturn(mMockUserManagerInternal) + .when(() -> LocalServices.getService(UserManagerInternal.class)); + doReturn(mMockIInputMethodManager) + .when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)); + doReturn(mMockIPlatformCompat) + .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + + // Stubbing out context related methods to avoid the system holding strong references to + // InputMethodManagerService. + doNothing().when(mContext).enforceCallingPermission(anyString(), anyString()); + doNothing().when(mContext).sendBroadcastAsUser(any(), any()); + doReturn(null).when(mContext).registerReceiver(any(), any()); + doReturn(null) + .when(mContext) + .registerReceiverAsUser(any(), any(), any(), anyString(), any(), anyInt()); + + // Injecting and mocked InputMethodBindingController and InputMethod. + mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod); + InputManager.resetInstance(mMockIInputManager); + synchronized (ImfLock.class) { + when(mMockInputMethodBindingController.getCurMethod()) + .thenReturn(mMockInputMethodInvoker); + when(mMockInputMethodBindingController.bindCurrentMethod()) + .thenReturn(SUCCESS_WAITING_IME_BINDING_RESULT); + doNothing().when(mMockInputMethodBindingController).unbindCurrentMethod(); + when(mMockInputMethodBindingController.getSelectedMethodId()) + .thenReturn(TEST_SELECTED_IME_ID); + } + + // Shuffling around all other initialization to make the test runnable. + when(mMockIInputManager.getInputDeviceIds()).thenReturn(new int[0]); + when(mMockIInputMethodManager.isImeTraceEnabled()).thenReturn(false); + when(mMockIPlatformCompat.isChangeEnabledByUid(anyLong(), anyInt())).thenReturn(true); + when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true); + when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean())) + .thenReturn(new int[] {0}); + when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true); + when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt())) + .thenReturn(Binder.getCallingUid()); + when(mMockWindowManagerInternal.onToggleImeRequested(anyBoolean(), any(), any(), anyInt())) + .thenReturn(TEST_IME_TARGET_INFO); + when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder); + + // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(), + // which is ok to be mocked out for now. + doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString())); + + mServiceThread = + new ServiceThread( + "TestServiceThread", + Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */ + false); + mInputMethodManagerService = + new InputMethodManagerService( + mContext, mServiceThread, mMockInputMethodBindingController); + + // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of + // InputMethodManagerService, which is closer to the real situation. + InputMethodManagerService.Lifecycle lifecycle = + new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService); + + // Public local InputMethodManagerService. + lifecycle.onStart(); + try { + // After this boot phase, services can broadcast Intents. + lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY); + } catch (SecurityException e) { + // Security exception to permission denial is expected in test, mocking out to ensure + // InputMethodManagerService as system ready state. + if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { + throw e; + } + } + + // Call InputMethodManagerService#addClient() as a preparation to start interacting with it. + mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0); + } + + @After + public void tearDown() { + if (mServiceThread != null) { + mServiceThread.quitSafely(); + } + + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } + } + + protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput) + throws RemoteException { + synchronized (ImfLock.class) { + verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0)) + .setCurrentMethodVisible(); + } + verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) + .showSoftInput(any(), anyInt(), any()); + } + + protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) + throws RemoteException { + synchronized (ImfLock.class) { + verify(mMockInputMethodBindingController, times(setNotVisible ? 1 : 0)) + .setCurrentMethodNotVisible(); + } + verify(mMockInputMethod, times(hideSoftInput ? 1 : 0)) + .hideSoftInput(any(), anyInt(), any()); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java new file mode 100644 index 000000000000..ffa272943455 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java @@ -0,0 +1,266 @@ +/* + * 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.inputmethod; + +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.when; + +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.inputmethod.EditorInfo; +import android.window.ImeOnBackInvokedDispatcher; + +import com.android.internal.inputmethod.IInputMethodClient; +import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; +import com.android.internal.inputmethod.IRemoteInputConnection; +import com.android.internal.inputmethod.InputBindResult; +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.StartInputFlags; +import com.android.internal.inputmethod.StartInputReason; +import com.android.server.wm.WindowManagerInternal; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; +import java.util.List; + +/** + * Test the behavior of {@link InputMethodManagerService#startInputOrWindowGainedFocus(int, + * IInputMethodClient, IBinder, int, int, int, EditorInfo, IRemoteInputConnection, + * IRemoteAccessibilityInputConnection, int, int, ImeOnBackInvokedDispatcher)}. + */ +@RunWith(Parameterized.class) +public class InputMethodManagerServiceWindowGainedFocusTest + extends InputMethodManagerServiceTestBase { + private static final String TAG = "IMMSWindowGainedFocusTest"; + + private static final int[] SOFT_INPUT_STATE_FLAGS = + new int[] { + SOFT_INPUT_STATE_UNSPECIFIED, + SOFT_INPUT_STATE_UNCHANGED, + SOFT_INPUT_STATE_HIDDEN, + SOFT_INPUT_STATE_ALWAYS_HIDDEN, + SOFT_INPUT_STATE_VISIBLE, + SOFT_INPUT_STATE_ALWAYS_VISIBLE + }; + private static final int[] SOFT_INPUT_ADJUST_FLAGS = + new int[] { + SOFT_INPUT_ADJUST_UNSPECIFIED, + SOFT_INPUT_ADJUST_RESIZE, + SOFT_INPUT_ADJUST_PAN, + SOFT_INPUT_ADJUST_NOTHING + }; + private static final int DEFAULT_SOFT_INPUT_FLAG = + StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR; + + @Parameterized.Parameters(name = "softInputState={0}, softInputAdjustment={1}") + public static List<Object[]> softInputModeConfigs() { + ArrayList<Object[]> params = new ArrayList<>(); + for (int softInputState : SOFT_INPUT_STATE_FLAGS) { + for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) { + params.add(new Object[] {softInputState, softInputAdjust}); + } + } + return params; + } + + private final int mSoftInputState; + private final int mSoftInputAdjustment; + + public InputMethodManagerServiceWindowGainedFocusTest( + int softInputState, int softInputAdjustment) { + mSoftInputState = softInputState; + mSoftInputAdjustment = softInputAdjustment; + } + + @Test + public void startInputOrWindowGainedFocus_forwardNavigation() throws RemoteException { + mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */); + + assertThat( + startInputOrWindowGainedFocus( + DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */)) + .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT); + + switch (mSoftInputState) { + case SOFT_INPUT_STATE_UNSPECIFIED: + boolean showSoftInput = mSoftInputAdjustment == SOFT_INPUT_ADJUST_RESIZE; + verifyShowSoftInput( + showSoftInput /* setVisible */, showSoftInput /* showSoftInput */); + // Soft input was hidden by default, so it doesn't need to call + // {@code IMS#hideSoftInput()}. + verifyHideSoftInput(!showSoftInput /* setNotVisible */, false /* hideSoftInput */); + break; + case SOFT_INPUT_STATE_VISIBLE: + case SOFT_INPUT_STATE_ALWAYS_VISIBLE: + verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */); + verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); + break; + case SOFT_INPUT_STATE_UNCHANGED: // Do nothing + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); + break; + case SOFT_INPUT_STATE_HIDDEN: + case SOFT_INPUT_STATE_ALWAYS_HIDDEN: + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + // Soft input was hidden by default, so it doesn't need to call + // {@code IMS#hideSoftInput()}. + verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */); + break; + default: + throw new IllegalStateException( + "Unhandled soft input mode: " + + InputMethodDebug.softInputModeToString(mSoftInputState)); + } + } + + @Test + public void startInputOrWindowGainedFocus_notForwardNavigation() throws RemoteException { + mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */); + + assertThat( + startInputOrWindowGainedFocus( + DEFAULT_SOFT_INPUT_FLAG, false /* forwardNavigation */)) + .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT); + + switch (mSoftInputState) { + case SOFT_INPUT_STATE_UNSPECIFIED: + boolean hideSoftInput = mSoftInputAdjustment != SOFT_INPUT_ADJUST_RESIZE; + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + // Soft input was hidden by default, so it doesn't need to call + // {@code IMS#hideSoftInput()}. + verifyHideSoftInput(hideSoftInput /* setNotVisible */, false /* hideSoftInput */); + break; + case SOFT_INPUT_STATE_VISIBLE: + case SOFT_INPUT_STATE_HIDDEN: + case SOFT_INPUT_STATE_UNCHANGED: // Do nothing + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); + break; + case SOFT_INPUT_STATE_ALWAYS_VISIBLE: + verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */); + verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); + break; + case SOFT_INPUT_STATE_ALWAYS_HIDDEN: + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + // Soft input was hidden by default, so it doesn't need to call + // {@code IMS#hideSoftInput()}. + verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */); + break; + default: + throw new IllegalStateException( + "Unhandled soft input mode: " + + InputMethodDebug.softInputModeToString(mSoftInputState)); + } + } + + @Test + public void startInputOrWindowGainedFocus_userNotRunning() throws RemoteException { + when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(false); + + assertThat( + startInputOrWindowGainedFocus( + DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */)) + .isEqualTo(InputBindResult.INVALID_USER); + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); + } + + @Test + public void startInputOrWindowGainedFocus_invalidFocusStatus() throws RemoteException { + int[] invalidImeClientFocus = + new int[] { + WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW, + WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH, + WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID + }; + InputBindResult[] inputBingResult = + new InputBindResult[] { + InputBindResult.NOT_IME_TARGET_WINDOW, + InputBindResult.DISPLAY_ID_MISMATCH, + InputBindResult.INVALID_DISPLAY_ID + }; + + for (int i = 0; i < invalidImeClientFocus.length; i++) { + when(mMockWindowManagerInternal.hasInputMethodClientFocus( + any(), anyInt(), anyInt(), anyInt())) + .thenReturn(invalidImeClientFocus[i]); + + assertThat( + startInputOrWindowGainedFocus( + DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */)) + .isEqualTo(inputBingResult[i]); + verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */); + verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */); + } + } + + private InputBindResult startInputOrWindowGainedFocus( + int startInputFlag, boolean forwardNavigation) { + int softInputMode = mSoftInputState | mSoftInputAdjustment; + if (forwardNavigation) { + softInputMode |= SOFT_INPUT_IS_FORWARD_NAVIGATION; + } + + Log.i( + TAG, + "startInputOrWindowGainedFocus() softInputStateFlag=" + + InputMethodDebug.softInputModeToString(mSoftInputState) + + ", softInputAdjustFlag=" + + InputMethodDebug.softInputModeToString(mSoftInputAdjustment)); + + return mInputMethodManagerService.startInputOrWindowGainedFocus( + StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */, + mMockInputMethodClient /* client */, + mWindowToken /* windowToken */, + startInputFlag /* startInputFlags */, + softInputMode /* softInputMode */, + 0 /* windowFlags */, + mEditorInfo /* editorInfo */, + mMockRemoteInputConnection /* inputConnection */, + mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */, + mTargetSdkVersion /* unverifiedTargetSdkVersion */, + mCallingUserId /* userId */, + mMockImeOnBackInvokedDispatcher /* imeDispatcher */); + } + + private void mockHasImeFocusAndRestoreImeVisibility(boolean restoreImeVisibility) { + when(mMockWindowManagerInternal.hasInputMethodClientFocus( + any(), anyInt(), anyInt(), anyInt())) + .thenReturn(WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS); + when(mMockWindowManagerInternal.shouldRestoreImeVisibility(any())) + .thenReturn(restoreImeVisibility); + } +} diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 16317fedc583..73b1907c9f42 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -78,6 +78,12 @@ android_test { "servicestests-core-utils", ], + java_resources: [ + ":apex.test", + ":test.rebootless_apex_v1", + ":test.rebootless_apex_v2", + ], + jni_libs: [ "libpsi", ], diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index cb14864876ab..2583f44787ad 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -325,7 +325,7 @@ public class DeviceIdleControllerTest { doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any()); doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any()); doNothing().when(mAlarmManager) - .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any(Handler.class)); doReturn(mock(Sensor.class)).when(mSensorManager) .getDefaultSensor(eq(Sensor.TYPE_SIGNIFICANT_MOTION), eq(true)); doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt()); @@ -1111,12 +1111,12 @@ public class DeviceIdleControllerTest { alarmManagerInOrder.verify(mAlarmManager).setWindow( eq(AlarmManager.ELAPSED_REALTIME), eq(idleAfterInactiveExpiryTime), - anyLong(), anyString(), any(), any()); + anyLong(), anyString(), any(), any(Handler.class)); // Maintenance alarm alarmManagerInOrder.verify(mAlarmManager).setWindow( eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(idleAfterInactiveExpiryTime + idlingTimeMs), - anyLong(), anyString(), any(), any()); + anyLong(), anyString(), any(), any(Handler.class)); final AlarmManager.OnAlarmListener progressionListener = alarmListenerCaptor.getAllValues().get(0); @@ -1130,7 +1130,7 @@ public class DeviceIdleControllerTest { alarmManagerInOrder.verify(mAlarmManager).setWindow( eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(mInjector.nowElapsed + idlingTimeMs), - anyLong(), anyString(), any(), any()); + anyLong(), anyString(), any(), any(Handler.class)); for (int i = 0; i < 2; ++i) { // IDLE->MAINTENANCE alarm @@ -1144,12 +1144,12 @@ public class DeviceIdleControllerTest { alarmManagerInOrder.verify(mAlarmManager).setWindow( eq(AlarmManager.ELAPSED_REALTIME), eq(maintenanceExpiryTime), - anyLong(), anyString(), any(), any()); + anyLong(), anyString(), any(), any(Handler.class)); // Set IDLE->MAINTENANCE alarmManagerInOrder.verify(mAlarmManager).setWindow( eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(maintenanceExpiryTime + idlingTimeMs), - anyLong(), anyString(), any(), any()); + anyLong(), anyString(), any(), any(Handler.class)); // MAINTENANCE->IDLE alarm mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting(); @@ -1159,7 +1159,7 @@ public class DeviceIdleControllerTest { alarmManagerInOrder.verify(mAlarmManager).setWindow( eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(mInjector.nowElapsed + idlingTimeMs), - anyLong(), anyString(), any(), any()); + anyLong(), anyString(), any(), any(Handler.class)); } } @@ -2019,7 +2019,8 @@ public class DeviceIdleControllerTest { final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor .forClass(AlarmManager.OnAlarmListener.class); doNothing().when(mAlarmManager).setWindow( - anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), any()); + anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), + any(Handler.class)); doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion_registration"), alarmListener.capture(), any()); @@ -2063,7 +2064,8 @@ public class DeviceIdleControllerTest { final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor .forClass(AlarmManager.OnAlarmListener.class); doNothing().when(mAlarmManager).setWindow( - anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), any()); + anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), + any(Handler.class)); doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion_registration"), alarmListener.capture(), any()); @@ -2130,7 +2132,7 @@ public class DeviceIdleControllerTest { eq(SensorManager.SENSOR_DELAY_NORMAL)); inOrder.verify(mAlarmManager).setWindow( anyInt(), eq(mInjector.nowElapsed + mConstants.MOTION_INACTIVE_TIMEOUT), anyLong(), - eq("DeviceIdleController.motion"), any(), any()); + eq("DeviceIdleController.motion"), any(), any(Handler.class)); final SensorEventListener listener = listenerCaptor.getValue(); // Trigger motion @@ -2140,7 +2142,7 @@ public class DeviceIdleControllerTest { final ArgumentCaptor<Long> registrationTimeCaptor = ArgumentCaptor.forClass(Long.class); inOrder.verify(mAlarmManager).setWindow( anyInt(), registrationTimeCaptor.capture(), anyLong(), - eq("DeviceIdleController.motion_registration"), any(), any()); + eq("DeviceIdleController.motion_registration"), any(), any(Handler.class)); // Make sure the listener is re-registered. mInjector.nowElapsed = registrationTimeCaptor.getValue(); @@ -2150,7 +2152,7 @@ public class DeviceIdleControllerTest { eq(SensorManager.SENSOR_DELAY_NORMAL)); final ArgumentCaptor<Long> timeoutCaptor = ArgumentCaptor.forClass(Long.class); inOrder.verify(mAlarmManager).setWindow(anyInt(), timeoutCaptor.capture(), anyLong(), - eq("DeviceIdleController.motion"), any(), any()); + eq("DeviceIdleController.motion"), any(), any(Handler.class)); // No motion before timeout stationaryListener.motionExpected = false; 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 5b7b8f4ca21f..4d92b7f480a4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -25,6 +25,7 @@ import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED; import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW; import static com.android.server.am.BroadcastQueueTest.getUidForPackage; import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver; +import static com.android.server.am.BroadcastQueueTest.withPriority; import static com.google.common.truth.Truth.assertThat; @@ -46,8 +47,10 @@ import android.media.AudioManager; import android.os.Bundle; import android.os.BundleMerger; import android.os.HandlerThread; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.IndentingPrintWriter; import androidx.test.filters.SmallTest; @@ -59,6 +62,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; @@ -283,7 +288,7 @@ public class BroadcastQueueModernImplTest { final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0); + queue.enqueueOrReplaceBroadcast(airplaneRecord, 0); queue.setProcessCached(false); final long notCachedRunnableAt = queue.getRunnableAt(); @@ -305,12 +310,12 @@ public class BroadcastQueueModernImplTest { // enqueue a bg-priority broadcast then a fg-priority one final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone); - queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0); + queue.enqueueOrReplaceBroadcast(timezoneRecord, 0); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0); + queue.enqueueOrReplaceBroadcast(airplaneRecord, 0); // verify that: // (a) the queue is immediately runnable by existence of a fg-priority broadcast @@ -339,9 +344,9 @@ public class BroadcastQueueModernImplTest { final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null, - List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), - makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1); + List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10), + withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true); + queue.enqueueOrReplaceBroadcast(airplaneRecord, 1); assertFalse(queue.isRunnable()); assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); @@ -363,7 +368,7 @@ public class BroadcastQueueModernImplTest { final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0); + queue.enqueueOrReplaceBroadcast(airplaneRecord, 0); mConstants.MAX_PENDING_BROADCASTS = 128; queue.invalidateRunnableAt(); @@ -377,6 +382,55 @@ public class BroadcastQueueModernImplTest { } /** + * Confirm that we always prefer running pending items marked as "urgent", + * then "normal", then "offload", dispatching by the relative ordering + * within each of those clustering groups. + */ + @Test + public void testMakeActiveNextPending() { + BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, + PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); + + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); + queue.enqueueOrReplaceBroadcast( + makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED) + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 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.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction()); + + // To maximize test coverage, dump current state; we're not worried + // about the actual output, just that we don't crash + queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED); + queue.dumpLocked(SystemClock.uptimeMillis(), + new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream()))); + + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction()); + queue.makeActiveNextPending(); + assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction()); + assertTrue(queue.isEmpty()); + } + + /** * 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/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index e1a4c1dd7256..fd605f779625 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -280,13 +280,13 @@ public class BroadcastQueueTest { constants.TIMEOUT = 100; constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0; final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) { - public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) { + public boolean shouldSkip(BroadcastRecord r, Object o) { // Ignored return false; } - public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) { + public String shouldSkipMessage(BroadcastRecord r, Object o) { // Ignored - return false; + return null; } }; final BroadcastHistory emptyHistory = new BroadcastHistory(constants) { @@ -503,6 +503,11 @@ public class BroadcastQueueTest { return ai; } + static ResolveInfo withPriority(ResolveInfo info, int priority) { + info.priority = priority; + return info; + } + static ResolveInfo makeManifestReceiver(String packageName, String name) { return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM); } @@ -1634,4 +1639,22 @@ public class BroadcastQueueTest { waitForIdle(); verify(mAms, never()).enqueueOomAdjTargetLocked(any()); } + + /** + * Verify that expected events are triggered when a broadcast is finished. + */ + @Test + public void testNotifyFinished() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + final BroadcastRecord record = makeBroadcastRecord(intent, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))); + enqueueBroadcast(record); + + waitForIdle(); + verify(mAms).notifyBroadcastFinishedLocked(eq(record)); + verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED), + eq(1), eq(0), anyLong()); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java index 11573c57b382..05ed0e236b4d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -24,9 +24,10 @@ import static android.content.Intent.ACTION_TIME_CHANGED; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; -import static com.android.server.am.BroadcastRecord.isPrioritized; +import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount; import static com.android.server.am.BroadcastRecord.isReceiverEquals; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -99,6 +100,16 @@ public class BroadcastRecordTest { assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0)))); assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10)))); assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10)))); + + assertArrayEquals(new int[] {-1}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 0)), false)); + assertArrayEquals(new int[] {-1}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), -10)), false)); + assertArrayEquals(new int[] {-1}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 10)), false)); } @Test @@ -111,6 +122,17 @@ public class BroadcastRecordTest { createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 10), createResolveInfo(PACKAGE3, getAppId(3), 10)))); + + assertArrayEquals(new int[] {-1,-1,-1}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 0), + createResolveInfo(PACKAGE2, getAppId(2), 0), + createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); + assertArrayEquals(new int[] {-1,-1,-1}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 10), + createResolveInfo(PACKAGE2, getAppId(2), 10), + createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); } @Test @@ -123,6 +145,19 @@ public class BroadcastRecordTest { createResolveInfo(PACKAGE1, getAppId(1), 0), createResolveInfo(PACKAGE2, getAppId(2), 0), createResolveInfo(PACKAGE3, getAppId(3), 10)))); + + assertArrayEquals(new int[] {0,1,2}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), -10), + createResolveInfo(PACKAGE2, getAppId(2), 0), + createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); + assertArrayEquals(new int[] {0,0,2,3,3}, + calculateBlockedUntilTerminalCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 0), + createResolveInfo(PACKAGE2, getAppId(2), 0), + createResolveInfo(PACKAGE3, getAppId(3), 10), + createResolveInfo(PACKAGE3, getAppId(3), 20), + createResolveInfo(PACKAGE3, getAppId(3), 20)), false)); } @Test @@ -543,4 +578,9 @@ public class BroadcastRecordTest { private static int getAppId(int i) { return Process.FIRST_APPLICATION_UID + i; } + + private static boolean isPrioritized(List<Object> receivers) { + return BroadcastRecord.isPrioritized( + calculateBlockedUntilTerminalCount(receivers, false), false); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 1753fc7a61e4..fc737d06059d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -41,6 +41,7 @@ import android.app.ActivityManagerInternal; import android.app.IActivityManager; import android.app.UiModeManager; import android.app.job.JobInfo; +import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; @@ -235,6 +236,59 @@ public class JobSchedulerServiceTest { mService.getMinJobExecutionGuaranteeMs(jobDef)); } + + /** + * Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)} + * returns a job with the correct delay and deadline constraints. + */ + @Test + public void testGetRescheduleJobForFailure() { + final long nowElapsed = sElapsedRealtimeClock.millis(); + final long initialBackoffMs = MINUTE_IN_MILLIS; + mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3; + + JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", + createJobInfo() + .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR)); + assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime()); + assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed()); + + // failure = 0, systemStop = 1 + JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob, + JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL); + assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); + assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); + + // failure = 0, systemStop = 2 + rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, + JobParameters.INTERNAL_STOP_REASON_PREEMPT); + // failure = 0, systemStop = 3 + rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, + JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED); + assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime()); + assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); + + // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO + for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) { + rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, + JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED); + } + assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); + assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); + + // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO + rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, + JobParameters.INTERNAL_STOP_REASON_TIMEOUT); + assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); + assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); + + // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO + rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); + assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime()); + assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed()); + } + /** * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job * with the correct delay and deadline constraints if the periodic job is scheduled with the @@ -544,14 +598,16 @@ public class JobSchedulerServiceTest { final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS; JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS)); - JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job); + JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob); assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime()); assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes - failedJob = mService.getRescheduleJobForFailureLocked(job); + failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob); @@ -559,7 +615,8 @@ public class JobSchedulerServiceTest { assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes - failedJob = mService.getRescheduleJobForFailureLocked(job); + failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob); @@ -569,7 +626,8 @@ public class JobSchedulerServiceTest { assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed()); advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes - failedJob = mService.getRescheduleJobForFailureLocked(job); + failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob); @@ -665,7 +723,8 @@ public class JobSchedulerServiceTest { public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() { JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS)); - JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job); + JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); long now = sElapsedRealtimeClock.millis(); long nextWindowStartTime = now + HOUR_IN_MILLIS; long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS; @@ -701,7 +760,8 @@ public class JobSchedulerServiceTest { JobStatus job = createJobStatus( "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob", createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS)); - JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job); + JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); // First window starts 30 minutes from now. advanceElapsedClock(30 * MINUTE_IN_MILLIS); long now = sElapsedRealtimeClock.millis(); @@ -742,7 +802,8 @@ public class JobSchedulerServiceTest { JobStatus job = createJobStatus( "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod", createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS)); - JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job); + JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job, + JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH); // First window starts 6.625 days from now. advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS); long now = sElapsedRealtimeClock.millis(); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java index 59cb43f16ef6..7c435be5ebdd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java @@ -432,10 +432,10 @@ public class BatteryControllerTest { assertFalse(topStartedJobs.contains(unrelatedJob)); // Job cleanup - mBatteryController.maybeStopTrackingJobLocked(batteryJob, null, false); - mBatteryController.maybeStopTrackingJobLocked(chargingJob, null, false); - mBatteryController.maybeStopTrackingJobLocked(bothPowerJob, null, false); - mBatteryController.maybeStopTrackingJobLocked(unrelatedJob, null, false); + mBatteryController.maybeStopTrackingJobLocked(batteryJob, null); + mBatteryController.maybeStopTrackingJobLocked(chargingJob, null); + mBatteryController.maybeStopTrackingJobLocked(bothPowerJob, null); + mBatteryController.maybeStopTrackingJobLocked(unrelatedJob, null); assertFalse(trackedJobs.contains(batteryJob)); assertFalse(trackedJobs.contains(chargingJob)); assertFalse(trackedJobs.contains(bothPowerJob)); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 674e500d2e41..3bee6871b4b9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -489,19 +489,22 @@ public class FlexibilityControllerTest { JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L); JobStatus js = createJobStatus("time", jb); js = new JobStatus( - js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, FROZEN_TIME, FROZEN_TIME); + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0, + FROZEN_TIME, FROZEN_TIME); assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); js = new JobStatus( - js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 3, FROZEN_TIME, FROZEN_TIME); + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1, + FROZEN_TIME, FROZEN_TIME); assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); js = new JobStatus( - js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 10, FROZEN_TIME, FROZEN_TIME); + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10, + FROZEN_TIME, FROZEN_TIME); assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS, mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0)); } @@ -637,7 +640,12 @@ public class FlexibilityControllerTest { JobInfo.Builder jb = createJob(0); JobStatus js = createJobStatus("time", jb); js = new JobStatus( - js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, FROZEN_TIME, FROZEN_TIME); + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0, + FROZEN_TIME, FROZEN_TIME); + assertFalse(js.hasFlexibilityConstraint()); + js = new JobStatus( + js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1, + FROZEN_TIME, FROZEN_TIME); assertFalse(js.hasFlexibilityConstraint()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 149ae0b81740..7f522b0a1af5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -248,20 +248,32 @@ public class JobStatusTest { // Less than 2 failures, priority shouldn't be affected. assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority()); - int backoffAttempt = 1; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + int numFailures = 1; + int numSystemStops = 0; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority()); // 2+ failures, priority should be lowered as much as possible. - backoffAttempt = 2; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 2; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority()); - backoffAttempt = 5; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 5; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority()); - backoffAttempt = 8; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 8; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority()); + + // System stops shouldn't factor in the downgrade. + numSystemStops = 10; + numFailures = 0; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); + assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority()); } @Test @@ -274,33 +286,48 @@ public class JobStatusTest { // Less than 2 failures, priority shouldn't be affected. assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority()); - int backoffAttempt = 1; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + int numFailures = 1; + int numSystemStops = 0; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority()); // Failures in [2,4), priority should be lowered slightly. - backoffAttempt = 2; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 2; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority()); - backoffAttempt = 3; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 3; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority()); // Failures in [4,6), priority should be lowered more. - backoffAttempt = 4; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 4; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); - backoffAttempt = 5; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 5; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); // 6+ failures, priority should be lowered as much as possible. - backoffAttempt = 6; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 6; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority()); - backoffAttempt = 12; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 12; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority()); + + // System stops shouldn't factor in the downgrade. + numSystemStops = 10; + numFailures = 0; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); + assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority()); } /** @@ -317,23 +344,36 @@ public class JobStatusTest { // Less than 6 failures, priority shouldn't be affected. assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); - int backoffAttempt = 1; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + int numFailures = 1; + int numSystemStops = 0; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); - backoffAttempt = 4; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 4; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); - backoffAttempt = 5; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 5; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); // 6+ failures, priority should be lowered as much as possible. - backoffAttempt = 6; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 6; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority()); - backoffAttempt = 12; - job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0); + numFailures = 12; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority()); + + // System stops shouldn't factor in the downgrade. + numSystemStops = 10; + numFailures = 0; + job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures, + numSystemStops, 0, 0); + assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority()); } /** diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java index bb477b18d5fa..b949b3b265af 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java @@ -47,6 +47,7 @@ import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedLis import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; +import android.os.Handler; import android.os.Looper; import android.os.Process; import android.os.SystemClock; @@ -276,13 +277,13 @@ public class PrefetchControllerTest { inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1)) .setWindow( anyInt(), eq(sElapsedRealtimeClock.millis() + 4 * HOUR_IN_MILLIS), - anyLong(), eq(TAG_PREFETCH), any(), any()); + anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class)); setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 3 * HOUR_IN_MILLIS); inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1)) .setWindow( anyInt(), eq(sElapsedRealtimeClock.millis() + 8 * HOUR_IN_MILLIS), - anyLong(), eq(TAG_PREFETCH), any(), any()); + anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class)); } @Test @@ -414,7 +415,7 @@ public class PrefetchControllerTest { verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1)) .setWindow( anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS), - anyLong(), eq(TAG_PREFETCH), any(), any()); + anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class)); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); assertFalse(jobStatus.isReady()); } @@ -464,7 +465,7 @@ public class PrefetchControllerTest { verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1)) .setWindow( anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS), - anyLong(), eq(TAG_PREFETCH), any(), any()); + anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class)); assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH)); assertFalse(jobStatus.isReady()); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 9407968dbb56..17822c6d5004 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -1356,7 +1356,7 @@ public class QuotaControllerTest { synchronized (mQuotaController.mLock) { assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); @@ -1441,7 +1441,7 @@ public class QuotaControllerTest { synchronized (mQuotaController.mLock) { assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2, mQuotaController.getMaxJobExecutionTimeMsLocked(job)); - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); @@ -1474,7 +1474,7 @@ public class QuotaControllerTest { synchronized (mQuotaController.mLock) { assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2, mQuotaController.getMaxJobExecutionTimeMsLocked(job)); - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); @@ -2017,7 +2017,7 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); } synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } advanceElapsedClock(15 * SECOND_IN_MILLIS); @@ -2032,7 +2032,7 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); } synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS); @@ -2090,7 +2090,7 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid); } synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false); + mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null); } advanceElapsedClock(15 * SECOND_IN_MILLIS); @@ -2105,9 +2105,9 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid); } synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false); + mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null); - mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false); + mQuotaController.maybeStopTrackingJobLocked(unaffected, null); assertTrue(mQuotaController.isWithinQuotaLocked(unaffected)); assertTrue(unaffected.isReady()); @@ -2226,8 +2226,8 @@ public class QuotaControllerTest { advanceElapsedClock(MINUTE_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null, false); - mQuotaController.maybeStopTrackingJobLocked(job2, null, false); + mQuotaController.maybeStopTrackingJobLocked(job1, null); + mQuotaController.maybeStopTrackingJobLocked(job2, null); assertFalse(mQuotaController .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); @@ -2312,8 +2312,8 @@ public class QuotaControllerTest { advanceElapsedClock(MINUTE_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null, false); - mQuotaController.maybeStopTrackingJobLocked(job2, null, false); + mQuotaController.maybeStopTrackingJobLocked(job1, null); + mQuotaController.maybeStopTrackingJobLocked(job2, null); assertFalse(mQuotaController .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); assertEquals(12, stats.jobCountLimit); @@ -2390,7 +2390,7 @@ public class QuotaControllerTest { advanceElapsedClock(MINUTE_IN_MILLIS); mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null, false); + mQuotaController.maybeStopTrackingJobLocked(job1, null); assertTrue(mQuotaController .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); assertEquals(4, stats.sessionCountLimit); @@ -2404,7 +2404,7 @@ public class QuotaControllerTest { advanceElapsedClock(MINUTE_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job2, null, false); + mQuotaController.maybeStopTrackingJobLocked(job2, null); assertFalse(mQuotaController .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); assertEquals(4, stats.sessionCountLimit); @@ -2708,7 +2708,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Test with timing sessions out of window but still under max execution limit. @@ -2725,7 +2725,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1), false); @@ -2734,7 +2734,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); synchronized (mQuotaController.mLock) { mQuotaController.prepareForExecutionLocked(jobStatus); @@ -2749,7 +2749,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } @Test @@ -2771,7 +2772,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -2782,7 +2783,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions in window but still in quota. final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); @@ -2796,7 +2797,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Add some more sessions, but still in quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2808,7 +2809,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test when out of quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2818,7 +2819,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { @@ -2826,7 +2828,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } @Test @@ -2850,7 +2853,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -2861,7 +2864,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions in window but still in quota. final long start = now - (6 * HOUR_IN_MILLIS); @@ -2873,7 +2876,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Add some more sessions, but still in quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2885,7 +2888,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test when out of quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2895,7 +2898,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { @@ -2903,7 +2907,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } /** @@ -2932,7 +2937,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -2943,7 +2948,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions in window but still in quota. final long start = now - (6 * HOUR_IN_MILLIS); @@ -2955,7 +2960,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Add some more sessions, but still in quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2967,7 +2972,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test when out of quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -2977,7 +2982,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { @@ -2985,7 +2991,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } @Test @@ -3013,7 +3020,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -3023,7 +3030,7 @@ public class QuotaControllerTest { mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions in window but still in quota. final long start = now - (6 * HOUR_IN_MILLIS); @@ -3039,7 +3046,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Add some more sessions, but still in quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -3051,7 +3058,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test when out of quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -3061,7 +3068,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { @@ -3069,7 +3077,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */ @@ -3108,7 +3117,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); inOrder.verify(mAlarmManager, timeout(1000).times(0)) .cancel(any(AlarmManager.OnAlarmListener.class)); @@ -3123,7 +3133,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS) @@ -3135,7 +3145,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedFrequentAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS) @@ -3146,7 +3156,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // And back up again. setStandbyBucket(FREQUENT_INDEX, jobStatus); @@ -3156,7 +3167,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedFrequentAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); setStandbyBucket(WORKING_INDEX, jobStatus); synchronized (mQuotaController.mLock) { @@ -3165,7 +3176,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); setStandbyBucket(ACTIVE_INDEX, jobStatus); synchronized (mQuotaController.mLock) { @@ -3173,7 +3184,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .cancel(any(AlarmManager.OnAlarmListener.class)); } @@ -3210,7 +3222,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Valid time in the future, so the count should be used. stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2; @@ -3221,7 +3233,7 @@ public class QuotaControllerTest { } verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); } /** @@ -3318,7 +3330,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() { @@ -3355,7 +3368,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } @Test @@ -3711,7 +3725,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } @@ -3737,7 +3751,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -3766,7 +3780,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { @@ -3774,11 +3788,11 @@ public class QuotaControllerTest { } advanceElapsedClock(20 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -3819,7 +3833,7 @@ public class QuotaControllerTest { long start = JobSchedulerService.sElapsedRealtimeClock.millis(); advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -3850,18 +3864,18 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); setDischarging(); start = JobSchedulerService.sElapsedRealtimeClock.millis(); advanceElapsedClock(20 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -3879,7 +3893,7 @@ public class QuotaControllerTest { setCharging(); advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } @@ -3905,7 +3919,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -3934,7 +3948,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { @@ -3942,11 +3956,11 @@ public class QuotaControllerTest { } advanceElapsedClock(20 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -3973,7 +3987,7 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } @@ -4005,7 +4019,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4030,11 +4044,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4063,7 +4077,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now start = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -4073,11 +4087,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4116,11 +4130,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobFg1, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobFg2, null); } assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4155,11 +4169,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } assertEquals(2, stats.jobCountInRateLimitingWindow); @@ -4193,7 +4207,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4218,11 +4232,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobTop, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4253,7 +4267,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } advanceElapsedClock(5 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); @@ -4270,12 +4284,12 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobTop, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); - mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); + mQuotaController.maybeStopTrackingJobLocked(jobFg1, null); } expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4312,7 +4326,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, job1, true); + mQuotaController.maybeStopTrackingJobLocked(job1, job1); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -4329,7 +4343,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job2, null, false); + mQuotaController.maybeStopTrackingJobLocked(job2, null); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -4348,7 +4362,7 @@ public class QuotaControllerTest { long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS; advanceElapsedClock(elapsedGracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job3, null, false); + mQuotaController.maybeStopTrackingJobLocked(job3, null); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + elapsedGracePeriodMs, 1)); assertEquals(expected, @@ -4373,7 +4387,7 @@ public class QuotaControllerTest { advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, remainingGracePeriod + 10 * SECOND_IN_MILLIS, 1)); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job4, job4, true); + mQuotaController.maybeStopTrackingJobLocked(job4, job4); } assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -4387,7 +4401,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job5, job5, true); + mQuotaController.maybeStopTrackingJobLocked(job5, job5); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -4611,7 +4625,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Ran jobs up to the job limit. All of them should be allowed to run. for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) { @@ -4624,13 +4638,13 @@ public class QuotaControllerTest { } advanceElapsedClock(SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } advanceElapsedClock(SECOND_IN_MILLIS); } // Start alarm shouldn't have been scheduled since the app was in quota up until this point. verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // The app is now out of job count quota JobStatus throttledJob = createJobStatus( @@ -4649,7 +4663,7 @@ public class QuotaControllerTest { final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed; verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); } /** @@ -4680,7 +4694,7 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Ran jobs up to the job limit. All of them should be allowed to run. for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) { @@ -4695,13 +4709,13 @@ public class QuotaControllerTest { } advanceElapsedClock(SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } advanceElapsedClock(SECOND_IN_MILLIS); } // Start alarm shouldn't have been scheduled since the app was in quota up until this point. verify(mAlarmManager, timeout(1000).times(0)).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // The app is now out of session count quota JobStatus throttledJob = createJobStatus( @@ -4721,7 +4735,7 @@ public class QuotaControllerTest { final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed; verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); } @Test @@ -5185,7 +5199,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Test with timing sessions out of window. final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -5196,7 +5211,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Test with timing sessions in window but still in quota. final long end = now - (22 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); @@ -5208,7 +5224,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Add some more sessions, but still in quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -5220,7 +5237,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Test when out of quota. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, @@ -5230,7 +5248,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. synchronized (mQuotaController.mLock) { @@ -5238,7 +5257,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */ @@ -5282,7 +5302,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); inOrder.verify(mAlarmManager, timeout(1000).times(0)) .cancel(any(AlarmManager.OnAlarmListener.class)); @@ -5297,7 +5318,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); setStandbyBucket(FREQUENT_INDEX); final long expectedFrequentAlarmTime = @@ -5308,7 +5329,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedFrequentAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); setStandbyBucket(RARE_INDEX); final long expectedRareAlarmTime = @@ -5319,7 +5340,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // And back up again. setStandbyBucket(FREQUENT_INDEX); @@ -5329,7 +5351,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedFrequentAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); setStandbyBucket(WORKING_INDEX); synchronized (mQuotaController.mLock) { @@ -5338,7 +5360,7 @@ public class QuotaControllerTest { } inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); setStandbyBucket(ACTIVE_INDEX); synchronized (mQuotaController.mLock) { @@ -5346,7 +5368,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); } inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .cancel(any(AlarmManager.OnAlarmListener.class)); } @@ -5388,7 +5411,8 @@ public class QuotaControllerTest { SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); } verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } /** Tests that TimingSessions aren't saved when the device is charging. */ @@ -5408,7 +5432,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } @@ -5434,7 +5458,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5464,7 +5488,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { @@ -5472,11 +5496,11 @@ public class QuotaControllerTest { } advanceElapsedClock(20 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); assertEquals(expected, @@ -5521,7 +5545,7 @@ public class QuotaControllerTest { long start = JobSchedulerService.sElapsedRealtimeClock.millis(); advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5553,18 +5577,18 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); setDischarging(); start = JobSchedulerService.sElapsedRealtimeClock.millis(); advanceElapsedClock(20 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5583,7 +5607,7 @@ public class QuotaControllerTest { setCharging(); advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -5610,7 +5634,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5640,7 +5664,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { @@ -5648,11 +5672,11 @@ public class QuotaControllerTest { } advanceElapsedClock(20 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); } expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); assertEquals(expected, @@ -5680,7 +5704,7 @@ public class QuotaControllerTest { setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); } assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); } @@ -5715,7 +5739,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5741,11 +5765,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -5775,7 +5799,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now start = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -5785,11 +5809,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); assertEquals(expected, @@ -5825,7 +5849,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5851,11 +5875,11 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobTop, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -5887,7 +5911,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); } advanceElapsedClock(5 * SECOND_IN_MILLIS); setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); @@ -5904,12 +5928,12 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobTop, null); } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); - mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); + mQuotaController.maybeStopTrackingJobLocked(jobFg1, null); } expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); assertEquals(expected, @@ -5946,7 +5970,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, job1, true); + mQuotaController.maybeStopTrackingJobLocked(job1, job1); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -5963,7 +5987,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job2, null, false); + mQuotaController.maybeStopTrackingJobLocked(job2, null); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -5981,7 +6005,7 @@ public class QuotaControllerTest { long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS; advanceElapsedClock(elapsedGracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job3, null, false); + mQuotaController.maybeStopTrackingJobLocked(job3, null); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -6005,7 +6029,7 @@ public class QuotaControllerTest { advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job4, job4, true); + mQuotaController.maybeStopTrackingJobLocked(job4, job4); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -6019,7 +6043,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job5, job5, true); + mQuotaController.maybeStopTrackingJobLocked(job5, job5); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -6049,7 +6073,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, job, true); + mQuotaController.maybeStopTrackingJobLocked(job, job); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -6066,7 +6090,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -6085,7 +6109,7 @@ public class QuotaControllerTest { long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS; advanceElapsedClock(elapsedGracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, null, false); + mQuotaController.maybeStopTrackingJobLocked(job, null); } expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -6110,7 +6134,7 @@ public class QuotaControllerTest { advanceElapsedClock(10 * SECOND_IN_MILLIS); expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1)); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, job, true); + mQuotaController.maybeStopTrackingJobLocked(job, job); } assertEquals(expected, mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -6124,7 +6148,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job, job, true); + mQuotaController.maybeStopTrackingJobLocked(job, job); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -6169,7 +6193,7 @@ public class QuotaControllerTest { // Wait for the grace period to expire so the handler can process the message. Thread.sleep(gracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, job1, true); + mQuotaController.maybeStopTrackingJobLocked(job1, job1); } assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -6189,7 +6213,7 @@ public class QuotaControllerTest { // Wait for the grace period to expire so the handler can process the message. Thread.sleep(gracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job2, null, false); + mQuotaController.maybeStopTrackingJobLocked(job2, null); } assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); @@ -6215,7 +6239,7 @@ public class QuotaControllerTest { Thread.sleep(2 * gracePeriodMs); advanceElapsedClock(gracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job3, job3, true); + mQuotaController.maybeStopTrackingJobLocked(job3, job3); } expected.add(createTimingSession(start, gracePeriodMs, 1)); assertEquals(expected, @@ -6243,7 +6267,7 @@ public class QuotaControllerTest { Thread.sleep(2 * gracePeriodMs); advanceElapsedClock(gracePeriodMs); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job4, job4, true); + mQuotaController.maybeStopTrackingJobLocked(job4, job4); } expected.add(createTimingSession(start, gracePeriodMs, 1)); assertEquals(expected, @@ -6270,7 +6294,7 @@ public class QuotaControllerTest { Thread.sleep(2 * gracePeriodMs); advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job5, job5, true); + mQuotaController.maybeStopTrackingJobLocked(job5, job5); } expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expected, @@ -6424,7 +6448,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobReg1, jobReg1, true); + mQuotaController.maybeStopTrackingJobLocked(jobReg1, jobReg1); } expectedRegular.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expectedRegular, @@ -6440,7 +6464,7 @@ public class QuotaControllerTest { } advanceElapsedClock(10 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobEJ1, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobEJ1, null); } expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); assertEquals(expectedRegular, @@ -6461,12 +6485,12 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobEJ2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobEJ2, null); } expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); advanceElapsedClock(5 * SECOND_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(jobReg2, null, false); + mQuotaController.maybeStopTrackingJobLocked(jobReg2, null); } expectedRegular.add( createTimingSession(start + 5 * SECOND_IN_MILLIS, 10 * SECOND_IN_MILLIS, 1)); @@ -6556,7 +6580,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5000); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(regJob, null, false); + mQuotaController.maybeStopTrackingJobLocked(regJob, null); } assertEquals(0, debit.getTallyLocked()); assertEquals(10 * MINUTE_IN_MILLIS, @@ -6570,7 +6594,7 @@ public class QuotaControllerTest { } advanceElapsedClock(5 * MINUTE_IN_MILLIS); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(eJob, null, false); + mQuotaController.maybeStopTrackingJobLocked(eJob, null); } assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked()); assertEquals(5 * MINUTE_IN_MILLIS, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java index 612e90671345..51d641bfb80d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java @@ -81,8 +81,7 @@ public class StateControllerTest { public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { } - public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, - boolean forUpdate) { + public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) { } public void dumpControllerStateLocked(IndentingPrintWriter pw, diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java index a7739ed2c169..aabec2206333 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 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. @@ -31,25 +31,29 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.apex.ApexInfo; import android.apex.ApexSessionInfo; import android.apex.ApexSessionParams; import android.apex.IApexService; -import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Build; import android.os.Environment; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; -import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.parsing.PackageParser2; -import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,88 +64,139 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; +import java.util.Objects; @SmallTest @Presubmit @RunWith(AndroidJUnit4.class) public class ApexManagerTest { + + @Rule + public final MockSystemRule mMockSystem = new MockSystemRule(); + private static final String TEST_APEX_PKG = "com.android.apex.test"; private static final String TEST_APEX_FILE_NAME = "apex.test.apex"; private static final int TEST_SESSION_ID = 99999999; private static final int[] TEST_CHILD_SESSION_ID = {8888, 7777}; private ApexManager mApexManager; - private Context mContext; private PackageParser2 mPackageParser2; private IApexService mApexService = mock(IApexService.class); + private PackageManagerService mPmService; + + private InstallPackageHelper mInstallPackageHelper; + @Before - public void setUp() throws RemoteException { - mContext = InstrumentationRegistry.getInstrumentation().getContext(); + public void setUp() throws Exception { ApexManager.ApexManagerImpl managerImpl = spy(new ApexManager.ApexManagerImpl()); doReturn(mApexService).when(managerImpl).waitForApexService(); + when(mApexService.getActivePackages()).thenReturn(new ApexInfo[0]); mApexManager = managerImpl; - mPackageParser2 = new TestPackageParser2(); - } - - @Test - public void testGetPackageInfo_setFlagsMatchActivePackage() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG, - ApexManager.MATCH_ACTIVE_PACKAGE); - - assertThat(activePair).isNotNull(); - assertThat(activePair.second.getPackageName()).contains(TEST_APEX_PKG); - - final var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG, - ApexManager.MATCH_FACTORY_PACKAGE); + mPackageParser2 = new PackageParser2(null, null, null, new PackageParser2.Callback() { + @Override + public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) { + return true; + } - assertThat(factoryPair).isNull(); + @Override + public boolean hasFeature(String feature) { + return true; + } + }); + + mMockSystem.system().stageNominalSystemState(); + mPmService = new PackageManagerService(mMockSystem.mocks().getInjector(), + false /*factoryTest*/, + MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint, + false /*isEngBuild*/, + false /*isUserDebugBuild*/, + Build.VERSION_CODES.CUR_DEVELOPMENT, + Build.VERSION.INCREMENTAL); + mMockSystem.system().validateFinalState(); + mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class)); + } + + @NonNull + private List<ApexManager.ScanResult> scanApexInfos(ApexInfo[] apexInfos) { + return mInstallPackageHelper.scanApexPackages(apexInfos, + ParsingPackageUtils.PARSE_IS_SYSTEM_DIR, + PackageManagerService.SCAN_AS_SYSTEM, mPackageParser2, + ParallelPackageParser.makeExecutorService()); + } + + @Nullable + private ApexManager.ScanResult findActive(@NonNull List<ApexManager.ScanResult> results) { + return results.stream() + .filter(it -> it.apexInfo.isActive) + .filter(it -> Objects.equals(it.packageName, TEST_APEX_PKG)) + .findFirst() + .orElse(null); + } + + @Nullable + private ApexManager.ScanResult findFactory(@NonNull List<ApexManager.ScanResult> results, + @NonNull String packageName) { + return results.stream() + .filter(it -> it.apexInfo.isFactory) + .filter(it -> Objects.equals(it.packageName, packageName)) + .findFirst() + .orElse(null); + } + + @NonNull + private AndroidPackage mockParsePackage(@NonNull PackageParser2 parser, + @NonNull ApexInfo apexInfo) { + var flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES; + try { + var parsedPackage = parser.parsePackage(new File(apexInfo.modulePath), flags, + /* useCaches= */ false); + ScanPackageUtils.applyPolicy(parsedPackage, + PackageManagerService.SCAN_AS_APEX | PackageManagerService.SCAN_AS_SYSTEM, + mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false); + // isUpdatedSystemApp is ignoreable above, only used for shared library adjustment + return parsedPackage.hideAsFinal(); + } catch (PackageManagerException e) { + throw new RuntimeException(e); + } } @Test - public void testGetPackageInfo_setFlagsMatchFactoryPackage() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG, - ApexManager.MATCH_FACTORY_PACKAGE); - - assertThat(factoryPair).isNotNull(); - assertThat(factoryPair.second.getPackageName()).contains(TEST_APEX_PKG); + public void testScanActivePackage() { + var apexInfos = createApexInfoForTestPkg(true, false); + var results = scanApexInfos(apexInfos); + var active = findActive(results); + var factory = findFactory(results, TEST_APEX_PKG); - final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG, - ApexManager.MATCH_ACTIVE_PACKAGE); + assertThat(active).isNotNull(); + assertThat(active.packageName).isEqualTo(TEST_APEX_PKG); - assertThat(activePair).isNull(); + assertThat(factory).isNull(); } @Test - public void testGetPackageInfo_setFlagsNone() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + public void testScanFactoryPackage() { + var apexInfos = createApexInfoForTestPkg(false, true); + var results = scanApexInfos(apexInfos); + var active = findActive(results); + var factory = findFactory(results, TEST_APEX_PKG); - assertThat(apexPackageInfo.getPackageInfo(TEST_APEX_PKG, 0)).isNull(); + assertThat(factory).isNotNull(); + assertThat(factory.packageName).contains(TEST_APEX_PKG); + + assertThat(active).isNull(); } @Test - public void testGetApexSystemServices() throws RemoteException { - ApexInfo[] apexInfo = new ApexInfo[] { + public void testGetApexSystemServices() { + ApexInfo[] apexInfo = new ApexInfo[]{ createApexInfoForTestPkg(false, true, 1), // only active apex reports apex-system-service createApexInfoForTestPkg(true, false, 2), }; - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo); mApexManager.notifyScanResult(scanResults); List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices(); @@ -151,73 +206,11 @@ public class ApexManagerTest { } @Test - public void testGetActivePackages() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.getActivePackages()).isNotEmpty(); - } - - @Test - public void testGetActivePackages_noneActivePackages() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.getActivePackages()).isEmpty(); - } - - @Test - public void testGetFactoryPackages() throws RemoteException { - ApexInfo [] apexInfo = createApexInfoForTestPkg(false, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.getFactoryPackages()).isNotEmpty(); - } - - @Test - public void testGetFactoryPackages_noneFactoryPackages() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.getFactoryPackages()).isEmpty(); - } - - @Test - public void testGetInactivePackages() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.getInactivePackages()).isNotEmpty(); - } - - @Test - public void testGetInactivePackages_noneInactivePackages() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.getInactivePackages()).isEmpty(); - } - - @Test - public void testIsApexPackage() throws RemoteException { - ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - - assertThat(apexPackageInfo.isApexPackage(TEST_APEX_PKG)).isTrue(); + public void testIsApexPackage() { + var apexInfos = createApexInfoForTestPkg(false, true); + var results = scanApexInfos(apexInfos); + var factory = findFactory(results, TEST_APEX_PKG); + assertThat(factory.pkg.isApex()).isTrue(); } @Test @@ -328,16 +321,14 @@ public class ApexManagerTest { assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG); ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo); mApexManager.notifyScanResult(scanResults); assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName)).isNull(); mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath(), "Some random error"); assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName)) - .isEqualTo("Some random error"); + .isEqualTo("Some random error"); } /** @@ -357,9 +348,7 @@ public class ApexManagerTest { when(fakeApkInApex.getPackageName()).thenReturn("randomPackageName"); ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true); - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo); mApexManager.notifyScanResult(scanResults); assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty(); @@ -371,11 +360,9 @@ public class ApexManagerTest { public void testInstallPackage_activeOnSystem() throws Exception { ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true, /* isFactory= */ true, extractResource("test.apex_rebootless_v1", - "test.rebootless_apex_v1.apex")); + "test.rebootless_apex_v1.apex")); ApexInfo[] apexInfo = new ApexInfo[]{activeApexInfo}; - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + var results = scanApexInfos(apexInfo); File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex"); ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true, @@ -384,32 +371,28 @@ public class ApexManagerTest { File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex"); newApexInfo = mApexManager.installPackage(installedApex); - apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2); - var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless", - ApexManager.MATCH_ACTIVE_PACKAGE); - assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath()); - assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2); + var newPkg = mockParsePackage(mPackageParser2, newApexInfo); + assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath()); + assertThat(newPkg.getLongVersionCode()).isEqualTo(2); - var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless", - ApexManager.MATCH_FACTORY_PACKAGE); - assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath); - assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1); - assertThat(factoryInfo.second.isSystem()).isTrue(); + var factoryPkg = mockParsePackage(mPackageParser2, + findFactory(results, "test.apex.rebootless").apexInfo); + assertThat(factoryPkg.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath); + assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1); + assertThat(factoryPkg.isSystem()).isTrue(); } @Test public void testInstallPackage_activeOnData() throws Exception { ApexInfo factoryApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ false, /* isFactory= */ true, extractResource("test.apex_rebootless_v1", - "test.rebootless_apex_v1.apex")); + "test.rebootless_apex_v1.apex")); ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true, /* isFactory= */ false, extractResource("test.apex.rebootless@1", - "test.rebootless_apex_v1.apex")); + "test.rebootless_apex_v1.apex")); ApexInfo[] apexInfo = new ApexInfo[]{factoryApexInfo, activeApexInfo}; - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + var results = scanApexInfos(apexInfo); File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex"); ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true, @@ -418,30 +401,20 @@ public class ApexManagerTest { File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex"); newApexInfo = mApexManager.installPackage(installedApex); - apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2); - var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless", - ApexManager.MATCH_ACTIVE_PACKAGE); - assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath()); - assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2); + var newPkg = mockParsePackage(mPackageParser2, newApexInfo); + assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath()); + assertThat(newPkg.getLongVersionCode()).isEqualTo(2); - var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless", - ApexManager.MATCH_FACTORY_PACKAGE); - assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath); - assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1); - assertThat(factoryInfo.second.isSystem()).isTrue(); + var factoryPkg = mockParsePackage(mPackageParser2, + findFactory(results, "test.apex.rebootless").apexInfo); + assertThat(factoryPkg.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath); + assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1); + assertThat(factoryPkg.isSystem()).isTrue(); } @Test public void testInstallPackageBinderCallFails() throws Exception { - ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true, - /* isFactory= */ false, extractResource("test.apex_rebootless_v1", - "test.rebootless_apex_v1.apex")); - ApexInfo[] apexInfo = new ApexInfo[]{activeApexInfo}; - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); - when(mApexService.installAndActivatePackage(anyString())).thenThrow( new RuntimeException("install failed :(")); @@ -452,14 +425,12 @@ public class ApexManagerTest { } @Test - public void testGetActivePackageNameForApexModuleName() throws Exception { + public void testGetActivePackageNameForApexModuleName() { final String moduleName = "com.android.module_name"; ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false); apexInfo[0].moduleName = moduleName; - ApexPackageInfo apexPackageInfo = new ApexPackageInfo(); - List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages( - apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService()); + List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo); mApexManager.notifyScanResult(scanResults); assertThat(mApexManager.getActivePackageNameForApexModuleName(moduleName)) @@ -472,12 +443,13 @@ public class ApexManagerTest { when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex}); final File backingApexFile = mApexManager.getBackingApexFile( - new File("/apex/" + TEST_APEX_PKG + "/apk/App/App.apk")); + new File(mMockSystem.system().getApexDirectory(), + TEST_APEX_PKG + "/apk/App/App.apk")); assertThat(backingApexFile.getAbsolutePath()).isEqualTo(apex.modulePath); } @Test - public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() throws Exception { + public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() { File result = mApexManager.getBackingApexFile( new File("/data/local/tmp/whatever/does-not-matter")); assertThat(result).isNull(); @@ -489,22 +461,23 @@ public class ApexManagerTest { when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex}); final File backingApexFile = mApexManager.getBackingApexFile( - new File("/apex/com.wrong.apex/apk/App")); + new File(mMockSystem.system().getApexDirectory(), "com.wrong.apex/apk/App")); assertThat(backingApexFile).isNull(); } @Test - public void testGetBackingApexFiles_topLevelApexDir_returnsNull() throws Exception { + public void testGetBackingApexFiles_topLevelApexDir_returnsNull() { assertThat(mApexManager.getBackingApexFile(Environment.getApexDirectory())).isNull(); assertThat(mApexManager.getBackingApexFile(new File("/apex/"))).isNull(); assertThat(mApexManager.getBackingApexFile(new File("/apex//"))).isNull(); } @Test - public void testGetBackingApexFiles_flattenedApex() throws Exception { + public void testGetBackingApexFiles_flattenedApex() { ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex(); final File backingApexFile = flattenedApexManager.getBackingApexFile( - new File("/apex/com.android.apex.cts.shim/app/CtsShim/CtsShim.apk")); + new File(mMockSystem.system().getApexDirectory(), + "com.android.apex.cts.shim/app/CtsShim/CtsShim.apk")); assertThat(backingApexFile).isNull(); } @@ -521,7 +494,7 @@ public class ApexManagerTest { } private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) { - File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); + File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME); ApexInfo apexInfo = new ApexInfo(); apexInfo.isActive = isActive; apexInfo.isFactory = isFactory; diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java index 47f449c7f897..1be7e2e6e30e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java @@ -223,7 +223,7 @@ public final class BackgroundDexOptServiceUnitTest { /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); runFullJob(mJobServiceForIdle, mJobParametersForIdle, - /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); } @@ -241,7 +241,7 @@ public final class BackgroundDexOptServiceUnitTest { assertThat(getFailedPackageNamesSecondary()).isEmpty(); runFullJob(mJobServiceForIdle, mJobParametersForIdle, - /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); @@ -256,7 +256,7 @@ public final class BackgroundDexOptServiceUnitTest { mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED; runFullJob(mJobServiceForIdle, mJobParametersForIdle, - /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK, + /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null); assertThat(getFailedPackageNamesPrimary()).isEmpty(); @@ -393,7 +393,7 @@ public final class BackgroundDexOptServiceUnitTest { mCancelThread.join(TEST_WAIT_TIMEOUT_MS); // Always reschedule for periodic job - verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true); + verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false); verifyLastControlDexOptBlockingCall(false); } @@ -421,7 +421,7 @@ public final class BackgroundDexOptServiceUnitTest { mCancelThread.join(TEST_WAIT_TIMEOUT_MS); // Always reschedule for periodic job - verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true); + verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false); verify(mDexOptHelper, never()).controlDexOptBlocking(true); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt index 2165301f56c2..15b4975bb2b9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt @@ -83,7 +83,7 @@ class InitAppsHelperTest { val pms = createPackageManagerService() assertThat(pms.isFirstBoot).isEqualTo(true) assertThat(pms.isDeviceUpgrading).isEqualTo(false) - val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null, + val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, listOf<ScanPartition>()) assertThat( initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE) @@ -98,7 +98,7 @@ class InitAppsHelperTest { val pms = createPackageManagerService() assertThat(pms.isFirstBoot).isEqualTo(false) assertThat(pms.isDeviceUpgrading).isEqualTo(true) - val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null, + val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, listOf<ScanPartition>()) assertThat( initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE) @@ -112,7 +112,7 @@ class InitAppsHelperTest { val pms = createPackageManagerService() assertThat(pms.isFirstBoot).isEqualTo(false) assertThat(pms.isDeviceUpgrading).isEqualTo(false) - val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null, + val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, listOf<ScanPartition>()) assertThat( initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE) @@ -126,7 +126,7 @@ class InitAppsHelperTest { val pms = createPackageManagerService() assertThat(pms.isFirstBoot).isEqualTo(false) assertThat(pms.isDeviceUpgrading).isEqualTo(true) - val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null, + val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, listOf<ScanPartition>()) assertThat( initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index dc7bcd625ee3..dd6c7334a45a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -78,14 +78,6 @@ import com.android.server.testutils.mock import com.android.server.testutils.nullable import com.android.server.testutils.whenever import com.android.server.utils.WatchedArrayMap -import java.io.File -import java.io.IOException -import java.nio.file.Files -import java.security.PublicKey -import java.security.cert.CertificateException -import java.util.Arrays -import java.util.Random -import java.util.concurrent.FutureTask import libcore.util.HexEncoding import org.junit.Assert import org.junit.rules.TestRule @@ -94,6 +86,14 @@ import org.junit.runners.model.Statement import org.mockito.AdditionalMatchers.or import org.mockito.Mockito import org.mockito.quality.Strictness +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.security.PublicKey +import java.security.cert.CertificateException +import java.util.Arrays +import java.util.Random +import java.util.concurrent.FutureTask /** * A utility for mocking behavior of the system and dependencies when testing PackageManagerService @@ -104,6 +104,9 @@ import org.mockito.quality.Strictness class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { private val random = Random() val mocks = Mocks() + + // TODO: getBackingApexFile does not handle paths that aren't /apex + val apexDirectory = File("/apex") val packageCacheDirectory: File = Files.createTempDirectory("packageCache").toFile() val rootDirectory: File = @@ -297,7 +300,9 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) whenever(mocks.systemConfig.defaultVrComponents).thenReturn(ArraySet()) whenever(mocks.systemConfig.hiddenApiWhitelistedApps).thenReturn(ArraySet()) + wheneverStatic { SystemProperties.set(anyString(), anyString()) }.thenDoNothing() wheneverStatic { SystemProperties.getBoolean("fw.free_cache_v2", true) }.thenReturn(true) + wheneverStatic { Environment.getApexDirectory() }.thenReturn(apexDirectory) wheneverStatic { Environment.getPackageCacheDirectory() }.thenReturn(packageCacheDirectory) wheneverStatic { SystemProperties.digestOf("ro.build.fingerprint") }.thenReturn("cacheName") wheneverStatic { Environment.getRootDirectory() }.thenReturn(rootDirectory) diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java index 2fac31e9e6fd..d477cb6f356c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java @@ -73,7 +73,12 @@ public class AgentTrendCalculatorTest { } @Override - long getHardSatiatedConsumptionLimit() { + long getMinSatiatedConsumptionLimit() { + return 0; + } + + @Override + long getMaxSatiatedConsumptionLimit() { return 0; } diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java index fb3e8f298424..84a61c7a21e5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java @@ -132,8 +132,10 @@ public class AlarmManagerEconomicPolicyTest { public void testDefaults() { assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, - mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -150,13 +152,15 @@ public class AlarmManagerEconomicPolicyTest { @Test public void testConstantsUpdating_ValidValues() { setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); - setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(25)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(25)); setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10)); setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9)); setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7)); assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(3), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -171,13 +175,15 @@ public class AlarmManagerEconomicPolicyTest { public void testConstantsUpdating_InvalidValues() { // Test negatives. setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5)); - setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(-5)); setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(-1)); setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2)); setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3)); assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -188,14 +194,16 @@ public class AlarmManagerEconomicPolicyTest { assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app")); // Test min+max reversed. - setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); - setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(5)); + setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(4)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(3)); setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10)); setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11)); setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13)); assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app")); assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java index 6da4ab714d7a..cad608f8ff59 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java @@ -155,9 +155,12 @@ public class CompleteEconomicPolicyTest { assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES + EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES - + EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, - mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES + + EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES + + EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -178,8 +181,10 @@ public class CompleteEconomicPolicyTest { public void testConstantsUpdated() { setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4)); setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(6)); - setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(24)); - setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(26)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(24)); + setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(26)); setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(9)); setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(11)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(8)); @@ -188,7 +193,8 @@ public class CompleteEconomicPolicyTest { setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(2)); assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(50), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -206,8 +212,10 @@ public class CompleteEconomicPolicyTest { setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, false); assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES, mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES, - mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -229,8 +237,10 @@ public class CompleteEconomicPolicyTest { setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, true); assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES, - mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMaxSatiatedConsumptionLimit()); when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES, diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java index b7bbcd7562cd..ebf760cdf857 100644 --- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java @@ -132,8 +132,10 @@ public class JobSchedulerEconomicPolicyTest { public void testDefaults() { assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES, mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES, - mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES, + mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); @@ -171,7 +173,8 @@ public class JobSchedulerEconomicPolicyTest { @Test public void testConstantsUpdating_ValidValues() { setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); - setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(25)); setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4)); @@ -179,7 +182,8 @@ public class JobSchedulerEconomicPolicyTest { arcToCake(1)); assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(2), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -198,7 +202,8 @@ public class JobSchedulerEconomicPolicyTest { public void testConstantsUpdating_InvalidValues() { // Test negatives. setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5)); - setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(-5)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(-5)); setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3)); @@ -206,7 +211,8 @@ public class JobSchedulerEconomicPolicyTest { arcToCake(-4)); assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); final String pkgRestricted = "com.pkg.restricted"; when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); @@ -221,14 +227,16 @@ public class JobSchedulerEconomicPolicyTest { mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater)); // Test min+max reversed. - setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5)); - setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(3)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(5)); + setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4)); + setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(3)); setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11)); setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13)); assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit()); - assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit()); + assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit()); assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted)); assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app")); assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted)); diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java index 00d7541a79dc..a3a49d7035d9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.content.Context; +import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.util.ArraySet; @@ -222,7 +223,8 @@ public class AlarmQueueTest { alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS); verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any()); + anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any( + Handler.class)); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java index 608b64e7d12a..0d14c9f02677 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java @@ -589,14 +589,14 @@ public class CountQuotaTrackerTest { // No sessions saved yet. mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions out of window. final long now = mInjector.getElapsedRealtime(); logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20); mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test with timing sessions in window but still in quota. final long start = now - (6 * HOUR_IN_MILLIS); @@ -604,25 +604,27 @@ public class CountQuotaTrackerTest { logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5); mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Add some more sessions, but still in quota. logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1); logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3); mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, never()).setWindow( - anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // Test when out of quota. logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1); mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, timeout(1000).times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); // Alarm already scheduled, so make sure it's not scheduled again. mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); verify(mAlarmManager, times(1)).setWindow( - anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } /** Tests that the start alarm is properly rescheduled if the app's category is changed. */ @@ -656,7 +658,8 @@ public class CountQuotaTrackerTest { mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, never()) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class)); // And down from there. @@ -665,41 +668,42 @@ public class CountQuotaTrackerTest { mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS); mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS); mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); // And back up again. mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(), - eq(TAG_QUOTA_CHECK), any(), any()); + eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); inOrder.verify(mAlarmManager, timeout(1000).times(1)) .cancel(any(AlarmManager.OnAlarmListener.class)); inOrder.verify(mAlarmManager, timeout(1000).times(0)) - .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); + .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), + any(Handler.class)); } @Test diff --git a/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt b/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt index 72ae77e48e25..a0a6743903ad 100644 --- a/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt +++ b/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt @@ -33,9 +33,14 @@ fun <T> wheneverStatic(mockedMethod: MockedMethod<T>) = object : CustomStaticStu override fun thenReturn(value: T) { ExtendedMockito.doReturn(value).wheneverStatic(mockedMethod) } + + override fun thenDoNothing() { + ExtendedMockito.doNothing().wheneverStatic(mockedMethod) + } } interface CustomStaticStubber<T> { fun thenAnswer(answer: Answer<T>) fun thenReturn(value: T) + fun thenDoNothing() } diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 8a932f165ca6..61bb57e145f8 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -124,9 +124,6 @@ android_test { ":PackageParserTestApp4", ":PackageParserTestApp5", ":PackageParserTestApp6", - ":apex.test", - ":test.rebootless_apex_v1", - ":test.rebootless_apex_v2", ":com.android.apex.cts.shim.v1_prebuilt", ":com.android.apex.cts.shim.v2_different_certificate_prebuilt", ":com.android.apex.cts.shim.v2_unsigned_apk_container_prebuilt", diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java index 0b84a60ae5a6..e6ab73a6f924 100644 --- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java @@ -49,6 +49,7 @@ import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; /** @@ -62,6 +63,7 @@ public class AnrHelperTest { private AnrHelper mAnrHelper; private ProcessRecord mAnrApp; + private ExecutorService mExecutorService; @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule(); @@ -88,7 +90,9 @@ public class AnrHelperTest { return mServiceThreadRule.getThread().getThreadHandler(); } }, mServiceThreadRule.getThread()); - mAnrHelper = new AnrHelper(service); + mExecutorService = mock(ExecutorService.class); + + mAnrHelper = new AnrHelper(service, mExecutorService); }); } @@ -119,7 +123,7 @@ public class AnrHelperTest { verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding( eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName), - eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), + eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mExecutorService), eq(false) /* onlyDumpSelf */); } @@ -133,7 +137,7 @@ public class AnrHelperTest { processingLatch.await(); return null; }).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(), - anyBoolean(), any(), anyBoolean()); + anyBoolean(), any(), any(), anyBoolean()); final ApplicationInfo appInfo = new ApplicationInfo(); final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive( "annotation"); @@ -155,6 +159,7 @@ public class AnrHelperTest { processingLatch.countDown(); // There is only one ANR reported. verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding( - anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean()); + anyString(), any(), any(), any(), anyBoolean(), any(), eq(mExecutorService), + anyBoolean()); } } diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java index 70519e401f45..9cada91f1db0 100644 --- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java @@ -45,6 +45,7 @@ import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.concurrent.ExecutorService; /** * Build/Install/Run: @@ -58,6 +59,7 @@ public class ProcessRecordTests { private ProcessRecord mProcessRecord; private ProcessErrorStateRecord mProcessErrorState; + private ExecutorService mExecutorService; @BeforeClass public static void setUpOnce() throws Exception { @@ -109,6 +111,7 @@ public class ProcessRecordTests { runWithDexmakerShareClassLoader(() -> { mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(), "name", 12345); + mExecutorService = mock(ExecutorService.class); mProcessErrorState = spy(mProcessRecord.mErrorState); doNothing().when(mProcessErrorState).startAppProblemLSP(); doReturn(false).when(mProcessErrorState).isSilentAnr(); @@ -194,11 +197,11 @@ public class ProcessRecordTests { assertTrue(mProcessRecord.isKilled()); } - private static void appNotResponding(ProcessErrorStateRecord processErrorState, + private void appNotResponding(ProcessErrorStateRecord processErrorState, String annotation) { TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation); processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */, null /* parentShortComponentName */, null /* parentProcess */, - false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */); + false /* aboveSystem */, timeoutRecord, mExecutorService, false /* onlyDumpSelf */); } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java index 8e6d90c839d8..3a9c0f0f1790 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java @@ -107,7 +107,7 @@ public class BaseClientMonitorTest { assertThat(mClientMonitor.getRequestId()).isEqualTo(id); } - private class TestClientMonitor extends BaseClientMonitor implements Interruptable { + private class TestClientMonitor extends BaseClientMonitor { public boolean mCanceled = false; TestClientMonitor() { @@ -129,5 +129,10 @@ public class BaseClientMonitorTest { public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) { mCanceled = true; } + + @Override + public boolean isInterruptable() { + return true; + } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java index 9e9d70332f00..3c77a3593001 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java @@ -57,11 +57,16 @@ public class BiometricSchedulerOperationTest { public interface FakeHal {} public abstract static class InterruptableMonitor<T> - extends HalClientMonitor<T> implements Interruptable { + extends HalClientMonitor<T> { public InterruptableMonitor() { super(null, null, null, null, 0, null, 0, 0, mock(BiometricLogger.class), mock(BiometricContext.class)); } + + @Override + public boolean isInterruptable() { + return true; + } } @Rule @@ -293,7 +298,6 @@ public class BiometricSchedulerOperationTest { assertThat(mOperation.isCanceling()).isTrue(); verify(mClientMonitor).cancel(); - verify(mClientMonitor, never()).cancelWithoutStarting(any()); verify(mClientMonitor, never()).destroy(); mStartedCallbackCaptor.getValue().onClientFinished(mClientMonitor, true); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index ffacbf331d89..9f30c7578540 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -34,7 +34,6 @@ 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.mockito.Mockito.withSettings; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; @@ -475,8 +474,8 @@ public class BiometricSchedulerTest { @Test public void testInterruptPrecedingClients_whenExpected() { - final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class, - withSettings().extraInterfaces(Interruptable.class)); + final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class); + when(interruptableMonitor.isInterruptable()).thenReturn(true); final BaseClientMonitor interrupter = mock(BaseClientMonitor.class); when(interrupter.interruptsPrecedingClients()).thenReturn(true); @@ -491,8 +490,8 @@ public class BiometricSchedulerTest { @Test public void testDoesNotInterruptPrecedingClients_whenNotExpected() { - final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class, - withSettings().extraInterfaces(Interruptable.class)); + final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class); + when(interruptableMonitor.isInterruptable()).thenReturn(true); final BaseClientMonitor interrupter = mock(BaseClientMonitor.class); when(interrupter.interruptsPrecedingClients()).thenReturn(false); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index 675f0e357aa8..608eeec72e58 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -620,6 +620,20 @@ public class FingerprintAuthenticationClientTest { verify(mCallback).onClientFinished(any(), eq(true)); } + @Test + public void sideFpsPowerPressCancelsIsntantly() throws Exception { + when(mSensorProps.isAnySidefpsType()).thenReturn(true); + + final FingerprintAuthenticationClient client = createClient(1); + client.start(mCallback); + + client.onPowerPressed(); + mLooper.dispatchAll(); + + verify(mCallback, never()).onClientFinished(any(), eq(true)); + verify(mCallback).onClientFinished(any(), eq(false)); + } + private FingerprintAuthenticationClient createClient() throws RemoteException { return createClient(100 /* version */, true /* allowBackgroundAuthentication */); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java index 2b069e354795..1fe267f65617 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java @@ -57,7 +57,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation; -import android.hardware.fingerprint.IUdfpsHbmListener; +import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.Handler; import android.os.IThermalEventListener; import android.os.IThermalService; @@ -1081,10 +1081,10 @@ public class DisplayModeDirectorTest { public void testUdfpsListenerGetsRegistered() { DisplayModeDirector director = createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0); - verify(mStatusBarMock, never()).setUdfpsHbmListener(any()); + verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any()); director.onBootCompleted(); - verify(mStatusBarMock).setUdfpsHbmListener(eq(director.getUdpfsObserver())); + verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver())); } @Test @@ -1093,10 +1093,9 @@ public class DisplayModeDirectorTest { createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0); director.start(createMockSensorManager()); director.onBootCompleted(); - ArgumentCaptor<IUdfpsHbmListener> captor = - ArgumentCaptor.forClass(IUdfpsHbmListener.class); - verify(mStatusBarMock).setUdfpsHbmListener(captor.capture()); - IUdfpsHbmListener hbmListener = captor.getValue(); + ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor = + ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class); + verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture()); // Should be no vote initially Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS); 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 1b867be81669..8f6bee170b0e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -92,7 +92,6 @@ public class HdmiControlServiceTest { HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK); - mLocalDeviceTypes.add(DEVICE_AUDIO_SYSTEM); mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, mLocalDeviceTypes, new FakeAudioDeviceVolumeManagerWrapper())); @@ -480,6 +479,7 @@ public class HdmiControlServiceTest { HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV, @@ -501,6 +501,7 @@ public class HdmiControlServiceTest { HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_1_4_B); mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( @@ -517,6 +518,7 @@ public class HdmiControlServiceTest { HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, HdmiControlManager.HDMI_CEC_VERSION_2_0); mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); HdmiCecMessage reportFeatures = ReportFeaturesMessage.build( diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java index 3f55f1bb76a4..ec61b877a3e4 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; +import android.content.pm.UserPackage; import androidx.test.runner.AndroidJUnit4; @@ -54,7 +55,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI addPackage(target(otherTarget), USER); addPackage(overlay(OVERLAY, TARGET), USER); - final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); + final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET)); // The result should be the same for every time assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages); @@ -67,7 +68,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI addPackage(target(TARGET), USER); addPackage(overlay(OVERLAY, TARGET), USER); - final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); + final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET)); configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */); expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages); @@ -101,7 +102,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI addPackage(overlay(OVERLAY, TARGET), USER); configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */); - final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); + final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET)); expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages); final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); @@ -133,7 +134,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI addPackage(target(TARGET), USER); addPackage(overlay(OVERLAY, TARGET), USER); - final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); + final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET)); final Consumer<ConfigState> setOverlay = (state -> { configureSystemOverlay(OVERLAY, state, 0 /* priority */); @@ -185,7 +186,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */); configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 1 /* priority */); - final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); + final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET)); expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages); final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); @@ -230,7 +231,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */); configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */); - final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER)); + final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET)); expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages); final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); 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 f69141db0872..ab5292834fe6 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java @@ -22,7 +22,6 @@ import static android.content.om.OverlayInfo.STATE_MISSING_TARGET; import static android.os.OverlayablePolicy.CONFIG_SIGNATURE; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -30,7 +29,7 @@ import static org.testng.Assert.assertThrows; import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; -import android.util.Pair; +import android.content.pm.UserPackage; import androidx.test.runner.AndroidJUnit4; @@ -66,7 +65,7 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes @Test public void testGetOverlayInfo() throws Exception { installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); final OverlayManagerServiceImpl impl = getImpl(); final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER); @@ -79,11 +78,11 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes @Test public void testGetOverlayInfosForTarget() throws Exception { installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY2, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY3, TARGET), USER2, - Set.of(new PackageAndUser(OVERLAY3, USER2), new PackageAndUser(TARGET, USER2))); + Set.of(UserPackage.of(USER2, OVERLAY3), UserPackage.of(USER2, TARGET))); final OverlayManagerServiceImpl impl = getImpl(); final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER); @@ -107,13 +106,13 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes @Test public void testGetOverlayInfosForUser() throws Exception { installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY2, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY3, TARGET2), USER, - Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET2, USER))); + Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET2))); final OverlayManagerServiceImpl impl = getImpl(); final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER); @@ -138,11 +137,11 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes @Test public void testPriority() throws Exception { installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY2, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY3, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET))); final OverlayManagerServiceImpl impl = getImpl(); final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER); @@ -152,15 +151,15 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); assertEquals(impl.setLowestPriority(IDENTIFIER3, USER), - Optional.of(new PackageAndUser(TARGET, USER))); + Optional.of(UserPackage.of(USER, TARGET))); assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2); assertEquals(impl.setHighestPriority(IDENTIFIER3, USER), - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER), - Optional.of(new PackageAndUser(TARGET, USER))); + Optional.of(UserPackage.of(USER, TARGET))); assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3); } @@ -170,47 +169,47 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes assertNull(impl.getOverlayInfo(IDENTIFIER, USER)); installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); assertState(STATE_MISSING_TARGET, IDENTIFIER, USER); installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_DISABLED, IDENTIFIER, USER); assertEquals(impl.setEnabled(IDENTIFIER, true, USER), - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_ENABLED, IDENTIFIER, USER); // target upgrades do not change the state of the overlay upgradeAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER)), - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET)), + Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_ENABLED, IDENTIFIER, USER); uninstallAndAssert(TARGET, USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_MISSING_TARGET, IDENTIFIER, USER); installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); assertState(STATE_ENABLED, IDENTIFIER, USER); } @Test public void testOnOverlayPackageUpgraded() throws Exception { installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); upgradeAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER)), - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET)), + Set.of(UserPackage.of(USER, TARGET))); // upgrade to a version where the overlay has changed its target upgradeAndAssert(overlay(OVERLAY, TARGET2), USER, - Set.of(new PackageAndUser(TARGET, USER)), - Set.of(new PackageAndUser(TARGET, USER), - new PackageAndUser(TARGET2, USER))); + Set.of(UserPackage.of(USER, TARGET)), + Set.of(UserPackage.of(USER, TARGET), + UserPackage.of(USER, TARGET2))); } @Test @@ -222,10 +221,10 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes // request succeeded, and there was a change that needs to be // propagated to the rest of the system installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); - assertEquals(Set.of(new PackageAndUser(TARGET, USER)), + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); + assertEquals(Set.of(UserPackage.of(USER, TARGET)), impl.setEnabled(IDENTIFIER, true, USER)); // request succeeded, but nothing changed @@ -239,9 +238,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER); installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); final FakeIdmapDaemon idmapd = getIdmapd(); final FakeDeviceState state = getState(); @@ -259,9 +258,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER); installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); final FakeIdmapDaemon idmapd = getIdmapd(); final FakeDeviceState state = getState(); @@ -276,9 +275,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes public void testConfigSignaturePolicyNoConfig() throws Exception { addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER); installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); final FakeIdmapDaemon idmapd = getIdmapd(); final FakeDeviceState state = getState(); @@ -292,9 +291,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes @Test public void testConfigSignaturePolicyNoRefPkg() throws Exception { installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); final FakeIdmapDaemon idmapd = getIdmapd(); final FakeDeviceState state = getState(); @@ -312,9 +311,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER); installAndAssert(target(TARGET), USER, - Set.of(new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, TARGET))); installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER, - Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER))); + Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET))); final FakeIdmapDaemon idmapd = getIdmapd(); final FakeDeviceState state = getState(); 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 301697d996d8..bba7669f3bbd 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -28,6 +28,7 @@ import android.content.om.OverlayIdentifier; import android.content.om.OverlayInfo; import android.content.om.OverlayInfo.State; import android.content.om.OverlayableInfo; +import android.content.pm.UserPackage; import android.os.FabricatedOverlayInfo; import android.os.FabricatedOverlayInternal; import android.text.TextUtils; @@ -164,7 +165,7 @@ class OverlayManagerServiceImplTestsBase { * @throws IllegalStateException if the package is currently installed */ void installAndAssert(@NonNull FakeDeviceState.PackageBuilder pkg, int userId, - @NonNull Set<PackageAndUser> onAddedUpdatedPackages) + @NonNull Set<UserPackage> onAddedUpdatedPackages) throws OperationFailedException { if (mState.select(pkg.packageName, userId) != null) { throw new IllegalStateException("package " + pkg.packageName + " already installed"); @@ -185,8 +186,8 @@ class OverlayManagerServiceImplTestsBase { * @throws IllegalStateException if the package is not currently installed */ void upgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId, - @NonNull Set<PackageAndUser> onReplacingUpdatedPackages, - @NonNull Set<PackageAndUser> onReplacedUpdatedPackages) + @NonNull Set<UserPackage> onReplacingUpdatedPackages, + @NonNull Set<UserPackage> onReplacedUpdatedPackages) throws OperationFailedException { final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId); if (replacedPackage == null) { @@ -207,7 +208,7 @@ class OverlayManagerServiceImplTestsBase { * @throws IllegalStateException if the package is not currently installed */ void uninstallAndAssert(@NonNull String packageName, int userId, - @NonNull Set<PackageAndUser> onRemovedUpdatedPackages) { + @NonNull Set<UserPackage> onRemovedUpdatedPackages) { final FakeDeviceState.Package pkg = mState.select(packageName, userId); if (pkg == null) { throw new IllegalStateException("package " + packageName + " not installed"); diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java index c321639ffe2e..1a8ef9e53593 100644 --- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java @@ -387,7 +387,7 @@ public class AppsFilterImplTest { // delete user when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST); - appsFilter.onUserDeleted(ADDED_USER); + appsFilter.onUserDeleted(mSnapshot, ADDED_USER); for (int subjectUserId : USER_ARRAY) { for (int otherUserId : USER_ARRAY) { @@ -925,7 +925,7 @@ public class AppsFilterImplTest { assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID, overlaySetting, actorSetting, SYSTEM_USER)); - appsFilter.removePackage(mSnapshot, targetSetting, false /* isReplace */); + appsFilter.removePackage(mSnapshot, targetSetting); // Actor loses visibility to the overlay via removal of the target assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting, @@ -1267,7 +1267,7 @@ public class AppsFilterImplTest { watcher.verifyNoChangeReported("get"); // remove a package - appsFilter.removePackage(mSnapshot, seesNothing, false /* isReplace */); + appsFilter.removePackage(mSnapshot, seesNothing); watcher.verifyChangeReported("removePackage"); } @@ -1337,7 +1337,7 @@ public class AppsFilterImplTest { target.getPackageName())); // New changes don't affect the snapshot - appsFilter.removePackage(mSnapshot, target, false); + appsFilter.removePackage(mSnapshot, target); assertTrue( appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation, target, diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index fdf9354747a0..080548520b0c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -74,6 +74,7 @@ import android.content.pm.Signature; import android.content.pm.SigningDetails; import android.content.pm.SigningInfo; import android.content.pm.UserInfo; +import android.content.pm.UserPackage; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Icon; @@ -97,7 +98,6 @@ import com.android.internal.infra.AndroidFuture; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.pm.LauncherAppsService.LauncherAppsImpl; -import com.android.server.pm.ShortcutUser.PackageWithUser; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.uri.UriPermissionOwner; import com.android.server.wm.ActivityTaskManagerInternal; @@ -692,9 +692,9 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected Map<String, PackageInfo> mInjectedPackages; - protected Set<PackageWithUser> mUninstalledPackages; - protected Set<PackageWithUser> mDisabledPackages; - protected Set<PackageWithUser> mEphemeralPackages; + protected Set<UserPackage> mUninstalledPackages; + protected Set<UserPackage> mDisabledPackages; + protected Set<UserPackage> mEphemeralPackages; protected Set<String> mSystemPackages; protected PackageManager mMockPackageManager; @@ -1200,28 +1200,28 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (ENABLE_DUMP) { Log.v(TAG, "Uninstall package " + packageName + " / " + userId); } - mUninstalledPackages.add(PackageWithUser.of(userId, packageName)); + mUninstalledPackages.add(UserPackage.of(userId, packageName)); } protected void installPackage(int userId, String packageName) { if (ENABLE_DUMP) { Log.v(TAG, "Install package " + packageName + " / " + userId); } - mUninstalledPackages.remove(PackageWithUser.of(userId, packageName)); + mUninstalledPackages.remove(UserPackage.of(userId, packageName)); } protected void disablePackage(int userId, String packageName) { if (ENABLE_DUMP) { Log.v(TAG, "Disable package " + packageName + " / " + userId); } - mDisabledPackages.add(PackageWithUser.of(userId, packageName)); + mDisabledPackages.add(UserPackage.of(userId, packageName)); } protected void enablePackage(int userId, String packageName) { if (ENABLE_DUMP) { Log.v(TAG, "Enable package " + packageName + " / " + userId); } - mDisabledPackages.remove(PackageWithUser.of(userId, packageName)); + mDisabledPackages.remove(UserPackage.of(userId, packageName)); } PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId, @@ -1239,17 +1239,17 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid); ret.applicationInfo.packageName = pi.packageName; - if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) { + if (mUninstalledPackages.contains(UserPackage.of(userId, packageName))) { ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } - if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) { + if (mEphemeralPackages.contains(UserPackage.of(userId, packageName))) { ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT; } if (mSystemPackages.contains(packageName)) { ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } ret.applicationInfo.enabled = - !mDisabledPackages.contains(PackageWithUser.of(userId, packageName)); + !mDisabledPackages.contains(UserPackage.of(userId, packageName)); if (getSignatures) { ret.signatures = null; diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 96c0d0ad3d96..b20c63ca504f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -83,6 +83,7 @@ import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; +import android.content.pm.UserPackage; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; @@ -107,7 +108,6 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.pm.ShortcutService.ConfigConstants; import com.android.server.pm.ShortcutService.FileOutputStreamWithPath; -import com.android.server.pm.ShortcutUser.PackageWithUser; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -4081,12 +4081,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_10, LAUNCHER_1), - PackageWithUser.of(USER_10, LAUNCHER_2)), + set(UserPackage.of(USER_10, LAUNCHER_1), + UserPackage.of(USER_10, LAUNCHER_2)), hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); @@ -4113,12 +4113,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_10, LAUNCHER_1), - PackageWithUser.of(USER_10, LAUNCHER_2)), + set(UserPackage.of(USER_10, LAUNCHER_1), + UserPackage.of(USER_10, LAUNCHER_2)), hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_1", "s0_2"); @@ -4145,12 +4145,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_10, LAUNCHER_1), - PackageWithUser.of(USER_10, LAUNCHER_2)), + set(UserPackage.of(USER_10, LAUNCHER_1), + UserPackage.of(USER_10, LAUNCHER_2)), hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); @@ -4176,11 +4176,11 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_10, LAUNCHER_2)), + set(UserPackage.of(USER_10, LAUNCHER_2)), hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); @@ -4205,11 +4205,11 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(CALLING_PACKAGE_1), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_10, LAUNCHER_2)), + set(UserPackage.of(USER_10, LAUNCHER_2)), hashSet(user10.getAllLaunchersForTest().keySet())); assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0), "s0_2"); @@ -4234,8 +4234,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(CALLING_PACKAGE_1), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals( set(), @@ -4263,8 +4263,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(set(), hashSet(user10.getAllPackagesForTest().keySet())); assertEquals( - set(PackageWithUser.of(USER_0, LAUNCHER_1), - PackageWithUser.of(USER_0, LAUNCHER_2)), + set(UserPackage.of(USER_0, LAUNCHER_1), + UserPackage.of(USER_0, LAUNCHER_2)), hashSet(user0.getAllLaunchersForTest().keySet())); assertEquals(set(), hashSet(user10.getAllLaunchersForTest().keySet())); @@ -5584,12 +5584,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3)); assertExistsAndShadow(user0.getAllLaunchersForTest().get( - PackageWithUser.of(USER_0, LAUNCHER_1))); + UserPackage.of(USER_0, LAUNCHER_1))); assertExistsAndShadow(user0.getAllLaunchersForTest().get( - PackageWithUser.of(USER_0, LAUNCHER_2))); + UserPackage.of(USER_0, LAUNCHER_2))); - assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3))); - assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1))); + assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3))); + assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1))); doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class), anyString()); @@ -6146,12 +6146,12 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2)); assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3)); assertExistsAndShadow(user0.getAllLaunchersForTest().get( - PackageWithUser.of(USER_0, LAUNCHER_1))); + UserPackage.of(USER_0, LAUNCHER_1))); assertExistsAndShadow(user0.getAllLaunchersForTest().get( - PackageWithUser.of(USER_0, LAUNCHER_2))); + UserPackage.of(USER_0, LAUNCHER_2))); - assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3))); - assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1))); + assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3))); + assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1))); doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class), anyString()); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index c78678431dac..15fd73cf4674 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -39,6 +39,7 @@ import android.content.LocusId; import android.content.pm.Capability; import android.content.pm.CapabilityParams; import android.content.pm.ShortcutInfo; +import android.content.pm.UserPackage; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; @@ -51,7 +52,6 @@ import android.test.MoreAsserts; import androidx.test.filters.SmallTest; import com.android.frameworks.servicestests.R; -import com.android.server.pm.ShortcutUser.PackageWithUser; import java.io.File; import java.io.FileWriter; @@ -2413,7 +2413,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { assertWith(mManager.getDynamicShortcuts()).isEmpty(); }); // Make package 1 ephemeral. - mEphemeralPackages.add(PackageWithUser.of(USER_0, CALLING_PACKAGE_1)); + mEphemeralPackages.add(UserPackage.of(USER_0, CALLING_PACKAGE_1)); runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> { diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java index 7731a326cf61..c2556e9267d5 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java @@ -44,7 +44,9 @@ import java.util.concurrent.ThreadLocalRandom; public class CpuWakeupStatsTest { private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device"; private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device"; - private static final String KERNEL_REASON_UNKNOWN = "unsupported-free-form-reason"; + private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device"; + private static final String KERNEL_REASON_UNSUPPORTED = "-1 test.alarm.device"; + private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device"; private static final int TEST_UID_1 = 13239823; private static final int TEST_UID_2 = 25268423; @@ -57,6 +59,7 @@ public class CpuWakeupStatsTest { @Test public void removesOldWakeups() { + // The xml resource doesn't matter for this test. final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1); final Set<Long> timestamps = new HashSet<>(); @@ -165,11 +168,36 @@ public class CpuWakeupStatsTest { obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN); - // Unrelated subsystems, should be ignored. + // Should be ignored as this type of wakeup is unsupported. obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4); // There should be nothing in the attribution map. assertThat(obj.mWakeupAttribution.size()).isEqualTo(0); } + + @Test + public void unsupportedAttribution() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3); + + long wakeupTime = 970934; + obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED); + + // Should be ignored as this type of wakeup is unsupported. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4); + + // There should be nothing in the attribution map. + assertThat(obj.mWakeupAttribution.size()).isEqualTo(0); + + wakeupTime = 883124; + obj.noteWakeupTimeAndReason(wakeupTime, 3, KERNEL_REASON_ABORT); + + // Should be ignored as this type of wakeup is unsupported. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 2, TEST_UID_1, TEST_UID_4); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 5, TEST_UID_3); + + // There should be nothing in the attribution map. + assertThat(obj.mWakeupAttribution.size()).isEqualTo(0); + } } diff --git a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java index 4762696d7c61..aafc16db50da 100644 --- a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java @@ -29,8 +29,10 @@ import org.junit.runners.Parameterized; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.List; @SmallTest @RunWith(Enclosed.class) @@ -51,17 +53,25 @@ public class EventLoggerTest { private StringWriter mTestStringWriter; private PrintWriter mTestPrintWriter; + private TestDumpSink mTestConsumer; private EventLogger mEventLogger; @Before public void setUp() { mTestStringWriter = new StringWriter(); mTestPrintWriter = new PrintWriter(mTestStringWriter); + mTestConsumer = new TestDumpSink(); mEventLogger = new EventLogger(EVENTS_LOGGER_SIZE, EVENTS_LOGGER_TAG); } @Test - public void testThatConsumeOfEmptyLoggerProducesEmptyList() { + public void testThatConsumerProducesEmptyListFromEmptyLog() { + mEventLogger.dump(mTestConsumer); + assertThat(mTestConsumer.getLastKnownConsumedEvents()).isEmpty(); + } + + @Test + public void testThatPrintWriterProducesEmptyListFromEmptyLog() { mEventLogger.dump(mTestPrintWriter); assertThat(mTestStringWriter.toString()).isEmpty(); } @@ -102,10 +112,12 @@ public class EventLoggerTest { }); } + private TestDumpSink mTestConsumer; private EventLogger mEventLogger; private final StringWriter mTestStringWriter; private final PrintWriter mTestPrintWriter; + private final EventLogger.Event[] mEventsToInsert; private final EventLogger.Event[] mExpectedEvents; @@ -119,11 +131,25 @@ public class EventLoggerTest { @Before public void setUp() { + mTestConsumer = new TestDumpSink(); mEventLogger = new EventLogger(EVENTS_LOGGER_SIZE, EVENTS_LOGGER_TAG); } @Test - public void testThatLoggingWorksAsExpected() { + public void testThatConsumerDumpsEventsAsExpected() { + for (EventLogger.Event event: mEventsToInsert) { + mEventLogger.enqueue(event); + } + + mEventLogger.dump(mTestConsumer); + + assertThat(mTestConsumer.getLastKnownConsumedEvents()) + .containsExactlyElementsIn(mExpectedEvents); + } + + + @Test + public void testThatPrintWriterDumpsEventsAsExpected() { for (EventLogger.Event event: mEventsToInsert) { mEventLogger.enqueue(event); } @@ -149,11 +175,27 @@ public class EventLoggerTest { return stringWriter.toString(); } - private static class TestEvent extends EventLogger.Event { + + private static final class TestEvent extends EventLogger.Event { @Override public String eventToString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } } + + private static final class TestDumpSink implements EventLogger.DumpSink { + + private final ArrayList<EventLogger.Event> mEvents = new ArrayList<>(); + + @Override + public void sink(String tag, List<EventLogger.Event> events) { + mEvents.clear(); + mEvents.addAll(events); + } + + public ArrayList<EventLogger.Event> getLastKnownConsumedEvents() { + return new ArrayList<>(mEvents); + } + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java index ef84a4bf61c6..e85b574baa22 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java @@ -24,7 +24,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -139,34 +138,12 @@ public class DimmerTests extends WindowTestsBase { } @Test - public void testDimAboveNoChildCreatesSurface() { - final float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, alpha); - - SurfaceControl dimLayer = getDimLayer(); - - assertNotNull("Dimmer should have created a surface", dimLayer); - - verify(mTransaction).setAlpha(dimLayer, alpha); - verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE); - } - - @Test - public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() { - float alpha = 0.8f; - mDimmer.dimAbove(mTransaction, alpha); - final SurfaceControl firstSurface = getDimLayer(); - - alpha = 0.9f; - mDimmer.dimAbove(mTransaction, alpha); - - assertEquals(firstSurface, getDimLayer()); - verify(mTransaction).setAlpha(firstSurface, 0.9f); - } - - @Test public void testUpdateDimsAppliesCrop() { - mDimmer.dimAbove(mTransaction, 0.8f); + TestWindowContainer child = new TestWindowContainer(mWm); + mHost.addChild(child, 0); + + final float alpha = 0.8f; + mDimmer.dimAbove(mTransaction, child, alpha); int width = 100; int height = 300; @@ -178,17 +155,6 @@ public class DimmerTests extends WindowTestsBase { } @Test - public void testDimAboveNoChildNotReset() { - mDimmer.dimAbove(mTransaction, 0.8f); - SurfaceControl dimLayer = getDimLayer(); - mDimmer.resetDimStates(); - - mDimmer.updateDims(mTransaction, new Rect()); - verify(mTransaction).show(getDimLayer()); - verify(mTransaction, never()).remove(dimLayer); - } - - @Test public void testDimAboveWithChildCreatesSurfaceAboveChild() { TestWindowContainer child = new TestWindowContainer(mWm); mHost.addChild(child, 0); 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 d4c9087a9a69..35b9710f5528 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1520,6 +1520,29 @@ public class TransitionTests extends WindowTestsBase { transition.abort(); } + @Test + public void testCollectReparentChange() { + registerTestTransitionPlayer(); + + // Reparent activity in transition. + final Task lastParent = createTask(mDisplayContent); + final Task newParent = createTask(mDisplayContent); + final ActivityRecord activity = createActivityRecord(lastParent); + doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval(); + doNothing().when(activity).setDropInputMode(anyInt()); + activity.mVisibleRequested = true; + + final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, + activity.mTransitionController, mWm.mSyncEngine); + activity.mTransitionController.moveToCollecting(transition); + transition.collect(activity); + activity.reparent(newParent, POSITION_TOP); + + // ChangeInfo#mCommonAncestor should be set after reparent. + final Transition.ChangeInfo change = transition.mChanges.get(activity); + assertEquals(newParent.getDisplayArea(), change.mCommonAncestor); + } + private static void makeTaskOrganized(Task... tasks) { final ITaskOrganizer organizer = mock(ITaskOrganizer.class); for (Task t : tasks) { diff --git a/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java new file mode 100644 index 000000000000..cea3d8736af9 --- /dev/null +++ b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.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.usb; + +import com.android.internal.util.dump.DualDumpOutputStream; +import com.android.server.utils.EventLogger; + +import java.util.List; + +/** + * Writes logs to {@link DualDumpOutputStream}. + * + * @see EventLogger.DumpSink + * @see DualDumpOutputStream + */ +final class DualOutputStreamDumpSink implements EventLogger.DumpSink { + + private final long mId; + private final DualDumpOutputStream mDumpOutputStream; + + /* package */ DualOutputStreamDumpSink(DualDumpOutputStream dualDumpOutputStream, long id) { + mDumpOutputStream = dualDumpOutputStream; + mId = id; + } + + /** + * {@inheritDoc} + */ + @Override + public void sink(String tag, List<EventLogger.Event> events) { + mDumpOutputStream.write("USB Event Log", mId, tag); + for (EventLogger.Event evt: events) { + mDumpOutputStream.write("USB Event", mId, evt.toString()); + } + } +} diff --git a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java b/services/usb/java/com/android/server/usb/UsbDeviceLogger.java deleted file mode 100644 index fab00bcda941..000000000000 --- a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.server.usb; - -import android.util.Log; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.dump.DualDumpOutputStream; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.LinkedList; - -/** -* Constructor UsbDeviceLogger class -*/ -public class UsbDeviceLogger { - private final Object mLock = new Object(); - - // ring buffer of events to log. - @GuardedBy("mLock") - private final LinkedList<Event> mEvents; - - private final String mTitle; - - // the maximum number of events to keep in log - private final int mMemSize; - - /** - * Constructor for Event class. - */ - public abstract static class Event { - // formatter for timestamps - private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS"); - - private final Calendar mCalendar; - - Event() { - mCalendar = Calendar.getInstance(); - } - - /** - * Convert event to String - * @return StringBuilder - */ - public String toString() { - return (new StringBuilder(String.format("%tm-%td %tH:%tM:%tS.%tL", - mCalendar, mCalendar, mCalendar, mCalendar, mCalendar, mCalendar))) - .append(" ").append(eventToString()).toString(); - } - - /** - * Causes the string message for the event to appear in the logcat. - * Here is an example of how to create a new event (a StringEvent), adding it to the logger - * (an instance of UsbDeviceLoggerr) while also making it show in the logcat: - * <pre> - * myLogger.log( - * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) ); - * </pre> - * @param tag the tag for the android.util.Log.v - * @return the same instance of the event - */ - public Event printLog(String tag) { - Log.i(tag, eventToString()); - return this; - } - - /** - * Convert event to String. - * This method is only called when the logger history is about to the dumped, - * so this method is where expensive String conversions should be made, not when the Event - * subclass is created. - * Timestamp information will be automatically added, do not include it. - * @return a string representation of the event that occurred. - */ - public abstract String eventToString(); - } - - /** - * Constructor StringEvent class - */ - public static class StringEvent extends Event { - private final String mMsg; - - public StringEvent(String msg) { - mMsg = msg; - } - - @Override - public String eventToString() { - return mMsg; - } - } - - /** - * Constructor for logger. - * @param size the maximum number of events to keep in log - * @param title the string displayed before the recorded log - */ - public UsbDeviceLogger(int size, String title) { - mEvents = new LinkedList<Event>(); - mMemSize = size; - mTitle = title; - } - - /** - * Constructor for logger. - * @param evt the maximum number of events to keep in log - */ - public synchronized void log(Event evt) { - synchronized (mLock) { - if (mEvents.size() >= mMemSize) { - mEvents.removeFirst(); - } - mEvents.add(evt); - } - } - - /** - * Constructor for logger. - * @param dump the maximum number of events to keep in log - * @param id the category of events - */ - public synchronized void dump(DualDumpOutputStream dump, long id) { - dump.write("USB Event Log", id, mTitle); - for (Event evt : mEvents) { - dump.write("USB Event", id, evt.toString()); - } - } -} diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index e90a376e90f8..1c081c1c153d 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -91,6 +91,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.utils.EventLogger; import com.android.server.wm.ActivityTaskManagerInternal; import java.io.File; @@ -213,7 +214,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private static Set<Integer> sDenyInterfaces; private HashMap<Long, FileDescriptor> mControlFds; - private static UsbDeviceLogger sEventLogger; + private static EventLogger sEventLogger; static { sDenyInterfaces = new HashSet<>(); @@ -238,7 +239,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser public void onUEvent(UEventObserver.UEvent event) { if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString()); if (sEventLogger != null) { - sEventLogger.log(new UsbDeviceLogger.StringEvent("USB UEVENT: " + sEventLogger.enqueue(new EventLogger.StringEvent("USB UEVENT: " + event.toString())); } else { if (DEBUG) Slog.d(TAG, "sEventLogger == null"); @@ -395,7 +396,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mUEventObserver.startObserving(USB_STATE_MATCH); mUEventObserver.startObserving(ACCESSORY_START_MATCH); - sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity"); + sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity"); } UsbProfileGroupSettingsManager getCurrentSettings() { @@ -837,7 +838,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser protected void sendStickyBroadcast(Intent intent) { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); - sEventLogger.log(new UsbDeviceLogger.StringEvent("USB intent: " + intent)); + sEventLogger.enqueue(new EventLogger.StringEvent("USB intent: " + intent)); } private void updateUsbFunctions() { @@ -2350,7 +2351,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser if (mHandler != null) { mHandler.dump(dump, "handler", UsbDeviceManagerProto.HANDLER); - sEventLogger.dump(dump, UsbHandlerProto.UEVENT); + sEventLogger.dump(new DualOutputStreamDumpSink(dump, UsbHandlerProto.UEVENT)); } dump.end(token); diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index 47b09fea91d2..f91666081e82 100644 --- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java @@ -62,6 +62,7 @@ import com.android.internal.util.XmlUtils; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.utils.EventLogger; import libcore.io.IoUtils; @@ -130,7 +131,7 @@ class UsbProfileGroupSettingsManager { @GuardedBy("mLock") private boolean mIsWriteSettingsScheduled; - private static UsbDeviceLogger sEventLogger; + private static EventLogger sEventLogger; /** * A package of a user. @@ -263,7 +264,7 @@ class UsbProfileGroupSettingsManager { mUsbHandlerManager = usbResolveActivityManager; - sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER, + sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbProfileGroupSettingsManager activity"); } @@ -970,7 +971,7 @@ class UsbProfileGroupSettingsManager { matches, mAccessoryPreferenceMap.get(new AccessoryFilter(accessory))); } - sEventLogger.log(new UsbDeviceLogger.StringEvent("accessoryAttached: " + intent)); + sEventLogger.enqueue(new EventLogger.StringEvent("accessoryAttached: " + intent)); resolveActivity(intent, matches, defaultActivity, null, accessory); } @@ -1524,7 +1525,8 @@ class UsbProfileGroupSettingsManager { } } - sEventLogger.dump(dump, UsbProfileGroupSettingsManagerProto.INTENT); + sEventLogger.dump(new DualOutputStreamDumpSink(dump, + UsbProfileGroupSettingsManagerProto.INTENT)); dump.end(token); } diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING index c083e9039731..af67637c3898 100644 --- a/services/voiceinteraction/TEST_MAPPING +++ b/services/voiceinteraction/TEST_MAPPING @@ -23,6 +23,14 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsLocalVoiceInteraction", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ] } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d314a6579b58..7be40b873bf8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1147,8 +1147,12 @@ public class CarrierConfigManager { "carrier_data_call_apn_retry_after_disconnect_long"; /** - * Data call setup permanent failure causes by the carrier + * Data call setup permanent failure causes by the carrier. + * + * @deprecated This API key was added in mistake and is not used anymore by the telephony data + * frameworks. */ + @Deprecated public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings"; @@ -2103,6 +2107,16 @@ public class CarrierConfigManager { * is immediately closed (disabling keep-alive). */ public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection"; + /** + * Waiting time in milliseconds used before releasing an MMS data call. Not tearing down an MMS + * data connection immediately helps to reduce the message delivering latency if messaging + * continues between all parties in the conversation since the same data connection can be + * reused for further messages. + * + * This timer will control how long the data call will be kept alive before being torn down. + */ + public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT = + "mms_network_release_timeout_millis_int"; /** * The flatten {@link android.content.ComponentName componentName} of the activity that can @@ -8436,7 +8450,8 @@ public class CarrierConfigManager { * * The syntax of the retry rule: * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities - * are supported. + * are supported. If the capabilities are not specified, then the retry rule only applies + * to the current failed APN used in setup data call request. * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]" * * 2. Retry based on {@link DataFailCause} @@ -8447,15 +8462,16 @@ public class CarrierConfigManager { * "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...], * [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]" * + * 4. Permanent fail causes (no timer-based retry) on the current failed APN. Retry interval + * is specified for retrying the next available APN. + * "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|65543|65547| + * 2252|2253|2254, retry_interval=2500" + * * For example, * "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached * network request is emergency, then retry data network setup every 1 second for up to 20 * times. * - * "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|2254 - * , maximum_retries=0" means for those fail causes, never retry with timers. Note that - * when environment changes, retry can still happen. - * * "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|" * "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000" * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s, @@ -8758,6 +8774,22 @@ public class CarrierConfigManager { "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long"; /** + * The amount of time in milliseconds within which the network must set up a slicing + * configuration for the premium capability after + * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} + * returns {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS}. + * During the setup time, calls to + * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} will return + * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}. + * If the network fails set up a slicing configuration for the premium capability within the + * setup time, subsequent purchase requests will be allowed to go through again. + * + * The default value is 5 minutes. + */ + public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = + "premium_capability_network_setup_time_millis_long"; + + /** * The URL to redirect to when the user clicks on the notification for a network boost via * premium capabilities after applications call * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}. @@ -9069,6 +9101,7 @@ public class CarrierConfigManager { sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT, -1); sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1); sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40); + sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000); sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, ""); sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, ""); sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, ""); @@ -9432,8 +9465,13 @@ public class CarrierConfigManager { sDefaults.putStringArray( KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] { "capabilities=eims, retry_interval=1000, maximum_retries=20", - "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|" - + "2253|2254, maximum_retries=0", // No retry for those causes + // Permanent fail causes. When setup data call fails with the following + // fail causes, telephony data frameworks will stop timer-based retry on + // the failed APN until power cycle, APM, or some special events. Note that + // even timer-based retry is not performed, condition-based (RAT changes, + // registration state changes) retry can still happen. + "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|" + + "-3|65543|65547|2252|2253|2254, retry_interval=2500", "capabilities=mms|supl|cbs, retry_interval=2000", "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|" + "5000|10000|15000|20000|40000|60000|120000|240000|" @@ -9474,6 +9512,8 @@ public class CarrierConfigManager { sDefaults.putLong( KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG, TimeUnit.MINUTES.toMillis(30)); + sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG, + TimeUnit.MINUTES.toMillis(5)); sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null); sDefaults.putBoolean(KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL, false); sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{ diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index d670e5592c42..1f301c1b2279 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -2268,7 +2268,21 @@ public final class SmsManager { RESULT_RIL_SIM_ABSENT, RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED, RESULT_RIL_ACCESS_BARRED, - RESULT_RIL_BLOCKED_DUE_TO_CALL + RESULT_RIL_BLOCKED_DUE_TO_CALL, + RESULT_RIL_GENERIC_ERROR, + RESULT_RIL_INVALID_RESPONSE, + RESULT_RIL_SIM_PIN2, + RESULT_RIL_SIM_PUK2, + RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE, + RESULT_RIL_SIM_ERROR, + RESULT_RIL_INVALID_SIM_STATE, + RESULT_RIL_NO_SMS_TO_ACK, + RESULT_RIL_SIM_BUSY, + RESULT_RIL_SIM_FULL, + RESULT_RIL_NO_SUBSCRIPTION, + RESULT_RIL_NO_NETWORK_FOUND, + RESULT_RIL_DEVICE_IN_USE, + RESULT_RIL_ABORTED }) @Retention(RetentionPolicy.SOURCE) public @interface Result {} @@ -2534,7 +2548,7 @@ public final class SmsManager { public static final int RESULT_RIL_SIM_ABSENT = 120; /** - * 1X voice and SMS are not allowed simulteneously. + * 1X voice and SMS are not allowed simultaneously. */ public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121; @@ -2553,6 +2567,73 @@ public final class SmsManager { */ public static final int RESULT_RIL_GENERIC_ERROR = 124; + /** + * A RIL internal error when one of the RIL layers receives an unrecognized response from a + * lower layer. + */ + public static final int RESULT_RIL_INVALID_RESPONSE = 125; + + /** + * Operation requires SIM PIN2 to be entered + */ + public static final int RESULT_RIL_SIM_PIN2 = 126; + + /** + * Operation requires SIM PUK2 to be entered + */ + public static final int RESULT_RIL_SIM_PUK2 = 127; + + /** + * Fail to find CDMA subscription from specified location + */ + public static final int RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE = 128; + + /** + * Received error from SIM card + */ + public static final int RESULT_RIL_SIM_ERROR = 129; + + /** + * Cannot process the request in current SIM state + */ + public static final int RESULT_RIL_INVALID_SIM_STATE = 130; + + /** + * ACK received when there is no SMS to ack + */ + public static final int RESULT_RIL_NO_SMS_TO_ACK = 131; + + /** + * SIM is busy + */ + public static final int RESULT_RIL_SIM_BUSY = 132; + + /** + * The target EF is full + */ + public static final int RESULT_RIL_SIM_FULL = 133; + + /** + * Device does not have subscription + */ + public static final int RESULT_RIL_NO_SUBSCRIPTION = 134; + + /** + * Network cannot be found + */ + public static final int RESULT_RIL_NO_NETWORK_FOUND = 135; + + /** + * Operation cannot be performed because the device is currently in use + */ + public static final int RESULT_RIL_DEVICE_IN_USE = 136; + + /** + * Operation aborted + */ + public static final int RESULT_RIL_ABORTED = 137; + + // SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION} /** diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index ef693b5278a0..76a145ce31cc 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -4002,6 +4002,10 @@ public class SubscriptionManager { * cautiously, for example, after formatting the number to a consistent format with * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}. * + * <p>The availability and correctness of the phone number depends on the underlying source + * and the network etc. Additional verification is needed to use this number for + * security-related or other sensitive scenarios. + * * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} * for the default one. * @return the phone number, or an empty string if not available. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index ff1b1c097d68..35b205592a6a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -9476,7 +9476,8 @@ public class TelephonyManager { ALLOWED_NETWORK_TYPES_REASON_USER, ALLOWED_NETWORK_TYPES_REASON_POWER, ALLOWED_NETWORK_TYPES_REASON_CARRIER, - ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G + ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, + ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS, }) @Retention(RetentionPolicy.SOURCE) public @interface AllowedNetworkTypesReason { @@ -9515,6 +9516,15 @@ public class TelephonyManager { public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; /** + * To indicate allowed network type change is requested by an update to the + * {@link android.os.UserManager.DISALLOW_CELLULAR_2G} user restriction. + * + * @hide + */ + @SystemApi + public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4; + + /** * Set the allowed network types of the device and provide the reason triggering the allowed * network change. * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or @@ -9606,6 +9616,7 @@ public class TelephonyManager { case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER: case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER: case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G: + case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS: return true; } return false; @@ -17195,7 +17206,13 @@ public class TelephonyManager { } /** - * Purchase premium capability request was successful. Subsequent attempts will return + * Purchase premium capability request was successful. + * Once the purchase result is successful, the network must set up a slicing configuration + * for the purchased premium capability within the timeout specified by + * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}. + * During the setup time, subsequent attempts will return + * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}. + * After setup is complete, subsequent attempts will return * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires. * The expiry time is determined by the type or duration of boost purchased from the carrier, * provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}. @@ -17319,6 +17336,16 @@ public class TelephonyManager { public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; /** + * Purchase premium capability was successful and is waiting for the network to setup the + * slicing configuration. If the setup is complete within the time specified by + * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}, + * subsequent requests will return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} + * until the purchase expires. If the setup is not complete within the time specified above, + * applications can reques the premium capability again. + */ + public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; + + /** * Results of the purchase premium capability request. * @hide */ @@ -17336,7 +17363,8 @@ public class TelephonyManager { PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED, PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE, PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED, - PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA}) + PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA, + PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}) public @interface PurchasePremiumCapabilityResult {} /** @@ -17377,6 +17405,8 @@ public class TelephonyManager { return "NETWORK_CONGESTED"; case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA: return "NOT_DEFAULT_DATA"; + case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP: + return "PENDING_NETWORK_SETUP"; default: return "UNKNOWN (" + result + ")"; } diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index e8642fef04f5..a3cbb4a436bc 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -458,8 +458,8 @@ public class ImsService extends Service { } } - private IImsRcsFeature createRcsFeatureInternal(int slotId, int subI) { - RcsFeature f = createRcsFeatureForSubscription(slotId, subI); + private IImsRcsFeature createRcsFeatureInternal(int slotId, int subId) { + RcsFeature f = createRcsFeatureForSubscription(slotId, subId); if (f != null) { f.setDefaultExecutor(getCachedExecutor()); setupFeature(f, slotId, ImsFeature.FEATURE_RCS); diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index f5b158fedd37..a42327b8a1a9 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -394,10 +394,12 @@ public abstract class ImsFeature { @VisibleForTesting public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { try { - // If we have just connected, send queued status. - c.notifyImsFeatureStatus(getFeatureState()); - // Add the callback if the callback completes successfully without a RemoteException. - mStatusCallbacks.register(c); + synchronized (mStatusCallbacks) { + // Add the callback if the callback completes successfully without a RemoteException + mStatusCallbacks.register(c); + // If we have just connected, send queued status. + c.notifyImsFeatureStatus(getFeatureState()); + } } catch (RemoteException e) { Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); } @@ -409,7 +411,9 @@ public abstract class ImsFeature { */ @VisibleForTesting public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { - mStatusCallbacks.unregister(c); + synchronized (mStatusCallbacks) { + mStatusCallbacks.unregister(c); + } } /** diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 98926710e053..9f612e6d7dd9 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -536,6 +536,12 @@ public interface RILConstants { int RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN = 230; int RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN = 231; int RIL_REQUEST_EXIT_EMERGENCY_MODE = 232; + int RIL_REQUEST_SET_SRVCC_CALL_INFO = 233; + int RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO = 234; + int RIL_REQUEST_START_IMS_TRAFFIC = 235; + int RIL_REQUEST_STOP_IMS_TRAFFIC = 236; + int RIL_REQUEST_SEND_ANBR_QUERY = 237; + int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; @@ -607,4 +613,7 @@ public interface RILConstants { int RIL_UNSOL_REGISTRATION_FAILED = 1104; int RIL_UNSOL_BARRING_INFO_CHANGED = 1105; int RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT = 1106; + int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107; + int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108; + int RIL_UNSOL_NOTIFY_ANBR = 1109; } 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 0837c0046183..56869655be98 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 @@ -44,7 +44,7 @@ class OpenAppAfterCameraTest_ShellTransit(testSpec: FlickerTestParameter) : OpenAppAfterCameraTest(testSpec) { @Before override fun before() { - Assume.assumeFalse(isShellTransitionsEnabled) + Assume.assumeTrue(isShellTransitionsEnabled) } @FlakyTest 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 73e6d223f824..5e6fc2129a6a 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 @@ -19,11 +19,11 @@ package com.android.server.wm.flicker.launch 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.helpers.setRotation import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule import org.junit.FixMethodOrder import org.junit.Test @@ -68,7 +68,6 @@ class OpenAppColdFromIcon(testSpec: FlickerTestParameter) : tapl.setExpectedRotation(Surface.ROTATION_0) } RemoveAllTasksButHomeRule.removeAllTasksButHome() - this.setRotation(testSpec.startRotation) } transitions { tapl @@ -187,7 +186,13 @@ class OpenAppColdFromIcon(testSpec: FlickerTestParameter) : @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<FlickerTestParameter> { - return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests() + return FlickerTestParameterFactory.getInstance() + // TAPL fails on landscape mode b/240916028 + .getConfigNonRotationTests( + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY + ) + ) } } } 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 09d7637ffefc..0edbc86ab65f 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 @@ -97,8 +97,8 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) : super.statusBarLayerPositionAtEnd() /** {@inheritDoc} */ - @Postsubmit @Test + @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end") override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() /** {@inheritDoc} */ 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 c10b99317b19..4ee12837fe09 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 @@ -16,6 +16,7 @@ package com.android.server.wm.flicker.launch +import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit import android.platform.test.annotations.RequiresDevice @@ -71,7 +72,7 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet } @Test - @Postsubmit + @FlakyTest(bugId = 227143265) fun showWhenLockedAppWindowBecomesVisible() { testSpec.assertWm { this.hasNoVisibleAppWindow() @@ -83,7 +84,7 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet } @Test - @Postsubmit + @FlakyTest(bugId = 227143265) fun showWhenLockedAppLayerBecomesVisible() { testSpec.assertLayers { this.isInvisible(showWhenLockedApp) @@ -98,11 +99,17 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible() /** {@inheritDoc} */ - @Postsubmit + @FlakyTest(bugId = 227143265) @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() = super.visibleLayersShownMoreThanOneConsecutiveEntry() + /** {@inheritDoc} */ + @FlakyTest(bugId = 209599395) + @Test + override fun navBarLayerIsVisibleAtStartAndEnd() = + super.navBarLayerIsVisibleAtStartAndEnd() + companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 6e935d11411e..83823eae993b 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -254,6 +254,8 @@ <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> + <meta-data android:name="android.app.shortcuts" + android:resource="@xml/shortcuts" /> </activity> <activity android:name=".SendNotificationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml new file mode 100644 index 000000000000..24830deac99f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml @@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Label for split screen shortcut--> + <string name="split_screen_shortcut_label">Split Screen Secondary Activity</string> +</resources>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml new file mode 100644 index 000000000000..804ec998477f --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> + <shortcut + android:shortcutId="split_screen_shortcut" + android:shortcutShortLabel="@string/split_screen_shortcut_label"> + <intent + android:action="android.intent.action.VIEW" + android:targetPackage="com.android.server.wm.flicker.testapp" + android:targetClass="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" /> + </shortcut> +</shortcuts>
\ No newline at end of file diff --git a/tests/RollbackTest/SampleRollbackApp/Android.bp b/tests/RollbackTest/SampleRollbackApp/Android.bp index a18488d8f57f..074c7bc39155 100644 --- a/tests/RollbackTest/SampleRollbackApp/Android.bp +++ b/tests/RollbackTest/SampleRollbackApp/Android.bp @@ -29,4 +29,5 @@ android_app { resource_dirs: ["res"], certificate: "platform", sdk_version: "system_current", + min_sdk_version: "29", } |